From e8514fd1db46b33a0038b4ccbf79f0409e062952 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 22:57:40 +0800 Subject: [PATCH 01/74] docs: diagnose version-bump cost anatomy and plan v0.0.3 architecture work Break down the v0.0.1->v0.0.2 +101k diff: ~92k is generated data, ~5k unrelated feature/cleanup work, and only ~2-3k human-written bump work. Split that residue into intrinsic cost (engine formula drift, reducible only in discovery) and self-inflicted cost (count-pins, per-version overlay copying, half-scripted drill, generated diff noise), then rank the v0.0.3 fixes: snapshot-blessed test pins, vendor calc-delta report, overlay-common layer, one-command bump script, gitattributes. --- docs/version-bump-architecture.md | 150 ++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docs/version-bump-architecture.md diff --git a/docs/version-bump-architecture.md b/docs/version-bump-architecture.md new file mode 100644 index 00000000..3d4c58c8 --- /dev/null +++ b/docs/version-bump-architecture.md @@ -0,0 +1,150 @@ +# Why version bumps look like 100k-line changes, and the v0.0.3 plan to fix it + +Status: **v0.0.3-dev milestone plan** (2026-07-16). Companion to +`adapting-to-0.5.4b.md` (which tracks the remaining 0.5.4b engine gaps). + +## 1. Anatomy of the v0.0.1 → v0.0.2 diff (the evidence) + +The release diff is `672 files, +101,068 / -25,386`. Broken down by what the +lines actually are: + +| Category | Lines | Human-written? | Version-bump related? | +|---|---|---|---| +| `data/*/generated/` (parsed_mods, special_vendor, coverage) | +91.8k / -14.5k | No — `regen-all.sh` output | Yes | +| Golden fixture recapture (18 × `meta.json`) | ±1.8k | No — `recapture_golden.py` output | Yes | +| Web/WASM `build_api` + web frontend | ~+5k | Yes | **No** — unrelated feature work | +| Legacy mod-parser deletion | -5k | Deletion | **No** — unrelated cleanup | +| Overlay curation (`special_mods.json` port, 27 entries + new) | +1.2k | **Yes, manual** | Yes | +| Count-pin / mirror-test repairs (parser_rules counts, coverage baseline float, spectre life, tier tests, precompile PATCH…) | ~dozens of edits scattered across ~10 files | **Yes, manual** | Yes | +| Engine formula adaptation (Mageblood model ~300 lines + 6 parity-fix commits) | ~1–2k | **Yes, manual** | Yes | + +So the honest number is: **the bump itself cost ~2–3k lines of human work, not +10k+**. The 10k+ perception comes from (a) generated artifacts committed to +git inflating every diff, and (b) unrelated feature/cleanup work riding the +same release branch. But the ~2–3k of human work is still real recurring cost, +and it splits into two very different kinds. + +## 2. Root causes + +### 2a. Intrinsic cost — engine formula drift (cannot be architected away) + +PoBR is a Rust port of PoB2's calc engine. When GGG rebalances mechanics +(0.5.4b reworked evasion/armour scaling, ailment magnitude, and PoB2 added +Mageblood legacy modeling), PoB2's community hand-edits its Lua, and PoBR must +hand-port the delta. No architecture eliminates this: **porting cost is +proportional to how much the game patch changes formulas**, and a data-only +patch costs zero here. + +What *is* fixable: today this cost is discovered by **archaeology** — flip the +golden, watch parity drop 96%→82%, then oracle-debug build by build until each +missing mechanic is found (that is how Mageblood was found). The discovery cost +dwarfs the porting cost. It should instead start from a **generated work list**. + +### 2b. Self-inflicted cost — everything else + +1. **Count-pins break mechanically every regen.** Tests pin exact counts and + floats (`real_data_section_counts` 202/682→201/687, coverage baseline + `0.801168`, spectre life 3.0→2.2, precompile PATCH, tier-degradation + version literals). Each data regen breaks them; each repair is a hand edit + with zero information content. +2. **Curated overlay entries are keyed per version-directory.** The 27 + hand-curated `special_mods.json` entries had to be copied + 4.5.0.3.4 → 4.5.4.3 by hand. They are vendor-semantics fixes, not + version-specific data — they should carry forward automatically. +3. **The bump drill is half-scripted.** `regen-all.sh` covers extraction, but + `gen-zh-cn.mjs`, `web pnpm sync-data`, vendor tarball swap + + `.pob2-version.txt`, golden recapture, and pin repairs are manual steps + living in a memory note. +4. **Generated JSON pollutes review.** 91.8k generated lines render in every + PR diff as if they were code. + +## 3. Target cost model (the contract v0.0.3 ships) + +| Patch type | Target human cost | +|---|---| +| Data-only patch (no formula changes) | One command + green CI + one small review. Hours, not days. | +| Balance patch (formula/constant changes) | One command + a generated vendor-delta work list; engine fixes sized by the list, each with its parity ratchet. | +| Major version (new mechanics) | Feature development — out of scope for "bump cost". | + +## 4. v0.0.3 work plan (ranked by leverage) + +### P0-1: Kill the count-pins — snapshot-bless instead of hand-pinned literals + +Replace every exact-count/float assertion tied to data content with a single +blessed snapshot per data version (e.g. +`data//generated/test_pins.json`), regenerated by a `--bless` path that +`regen-all.sh` runs as its last step. Tests compare against the snapshot; +a data regen updates the snapshot in the same commit, and review sees one +generated file change instead of ten hand edits. Assertions that guard +*structure* (schema, non-empty, monotonic ratchets) stay as code; assertions +that guard *counts of current data* move to the snapshot. + +Inventory to migrate (from the 4.5.4.3 drill): `parser_rules +real_data_section_counts`, precompile coverage baseline, precompile PATCH pin, +spectre life expectation, `mod_parser.rs` any-attribute expectation, +tier-degradation version literals, `contract_golden` where data-derived. + +### P0-2: Vendor calc-delta report — turn engine adaptation into a checklist + +`pipeline/diff-vendor-calcs.sh `: clones/checkouts both +vendor pins and emits a triage report: + +- `git diff --stat` + hunks for `Modules/Calc{Offence,Defence,Perform,Setup}.lua`, + `ModParser.lua`, `Modules/Data/` — the files PoBR mirrors. +- Diff of the *extracted* overlays (run `extract-lua` against both pins, diff + the JSON) — new/changed specialModList entries, parser rules, uniques. +- Output lands in `devs/docs/audits/vendor-delta-.md` as the Phase-1 work + list, replacing parity-drop archaeology. + +This is a wrapper around git-diff + existing extract-lua tooling — no new +parsing. The report is read by a human (or agent) to size and order the +engine work *before* flipping the golden. + +### P1-3: Version-independent curated overlay (`data/overlay-common/`) + +Split overlays into two layers merged by `pobr-gamedata` at load: +`data/overlay-common/` (hand-curated, vendor-semantics fixes — the 27 ported +special_mods entries and future ones) applied first, then +`data//overlay/` (version-specific, mostly extractor output) on top. +A new version inherits the curated layer for free; only genuinely +version-specific corrections live in the version dir. Loader change is one +merge step in `pobr-gamedata`; entries that stop matching new data degrade to +warnings (existing soft-degrade convention). + +### P1-4: One-command bump — `pipeline/bump-version.sh` + +Fold the whole drill (memory-note steps → script): +`query-patch-version` → config patch → `download-index` → `pathofexile-dat` → +vendor tarball swap + `.pob2-version.txt` → `regen-all.sh` (already includes +tree-snapshot carry-forward, special-mods, precompile, and after P0-1 the pin +bless) → `gen-zh-cn.mjs` → `web pnpm sync-data` → `recapture_golden.py` → +targeted test gate (`driver.sh versions` + parity + gamedata). Idempotent, +resumable per step (reuse `soft_step`), prints a manual-decision summary at +the end (golden flip is a *decision*, not automated — see +`adapting-to-0.5.4b.md` for why). + +### P2-5: Mark generated files in `.gitattributes` + +`data/**/generated/** linguist-generated -diff merge=ours` (and fixture +`meta.json` recaptures similarly). PRs stop rendering 90k generated lines; +review attention goes to the human-written residue. Three lines. + +### Explicitly NOT doing (YAGNI, revisit only with evidence) + +- **Data-driving formulas/constants out of Rust.** 0.5.4b's changes were + structural (evasion rework, Mageblood modeling), not constant tweaks — a + constants file would not have saved the work, and PoB2 itself hardcodes + these in Lua. Revisit only for a constant that has demonstrably changed + across ≥2 patches. +- **Auto-porting Lua formula code.** Transpiling vendor calc Lua is a research + project; the delta report (P0-2) captures 90% of the value. +- **Not committing generated data.** Runtime/deploy needs it; `-diff` + attributes solve the review noise at zero risk. + +## 5. Sequencing + +P0-1 and P0-2 are independent — do first, in either order or parallel. +P1-3 and P1-4 build on P0-1 (bless step slots into the script). P2-5 anytime. +The next real GGG patch is the acceptance test: run `bump-version.sh`, and the +human diff should be ~zero for the data layer plus a delta-report-sized +engine work list. From c2d2672360add998802f7ab8a87ec1297ee165f5 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:02:13 +0800 Subject: [PATCH 02/74] chore: mark generated data artifacts linguist-generated/-diff Generated JSON (data/**/generated, extractor overlay output, recaptured fixture meta.json) stays committed for runtime/deploy, but no longer renders as reviewable diff noise. Hand-curated overlay special_mods.json is explicitly exempted. --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..60e1a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Tool-generated artifacts: keep them committed (runtime/deploy needs them), +# but collapse them in diffs/PR review and language stats. +data/**/generated/** linguist-generated -diff +data/**/overlay/** linguist-generated +# Hand-curated overlay entries must stay reviewable. +data/**/overlay/special_mods.json -linguist-generated diff +examples/demo-bd-test/builds/*/meta.json linguist-generated -diff From 1a74df11ceecc996fe243011abd3618fb9679b84 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:18:39 +0800 Subject: [PATCH 03/74] feat(gamedata): add test-pin snapshot helper with POBR_BLESS_PINS Data-content count pins (section counts, coverage floats, data-derived values) break mechanically on every data regen and their repair carries zero information. Introduce data//generated/test_pins.json (flat map: pin name -> JSON value) plus a small helper: - test_pins::assert_pin(version_dir, name, actual) compares against the blessed snapshot and fails with a bless hint on mismatch; - POBR_BLESS_PINS=1 writes the actual value back (insta-style, no new dependency; write is serialized in-process, bless via cargo test). Structural guards (schemas, monotonic ratchets, invariants) stay as code assertions by design. --- crates/pobr-gamedata/src/lib.rs | 1 + crates/pobr-gamedata/src/test_pins.rs | 123 ++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 crates/pobr-gamedata/src/test_pins.rs diff --git a/crates/pobr-gamedata/src/lib.rs b/crates/pobr-gamedata/src/lib.rs index ff2345b2..86f0ed77 100644 --- a/crates/pobr-gamedata/src/lib.rs +++ b/crates/pobr-gamedata/src/lib.rs @@ -16,6 +16,7 @@ pub mod manifest; pub mod overlay; pub mod paths; pub mod ruleset; +pub mod test_pins; use std::fmt; use std::fs; diff --git a/crates/pobr-gamedata/src/test_pins.rs b/crates/pobr-gamedata/src/test_pins.rs new file mode 100644 index 00000000..d0a9ca99 --- /dev/null +++ b/crates/pobr-gamedata/src/test_pins.rs @@ -0,0 +1,123 @@ +//! Test-pin snapshots:数据内容计数钉的 bless 快照(v0.0.3 P0-1)。 +//! +//! 「断言值 = 当前数据文件内容的计数/统计量」这类测试钉(段条数、覆盖率、某条 +//! 数据的具体数值)随每次数据 regen 必然变化,手工重钉是零信息量劳动。此类钉 +//! 统一存进 `data//generated/test_pins.json`(扁平 map:pin 名 → JSON 值), +//! 测试经 [`assert_pin`] 对照;regen 后以 `POBR_BLESS_PINS=1` 重跑同一批测试 +//! 一步刷新(`pipeline/regen-all.sh` 末步已编排)。 +//! +//! 甄别边界:**结构性守卫**(schema 非空、单调棘轮、去重不变式)仍写死在代码 +//! 里,不进快照——快照只承载「会随数据内容漂移、且漂移本身无对错」的值。 +//! +//! 注意:bless 写回在进程内串行(全局锁);跨进程并发 bless 同一文件会整文件 +//! 互相覆盖。请用 `cargo test`(单进程多线程)而非 nextest(进程/测试)执行 +//! bless 命令。 + +use std::collections::BTreeMap; +use std::path::Path; +use std::sync::Mutex; + +use serde_json::Value; + +/// bless 开关环境变量:`POBR_BLESS_PINS=1` 时 [`assert_pin`] 把实际值写回快照。 +pub const BLESS_ENV: &str = "POBR_BLESS_PINS"; + +/// bless 写回锁(同进程内多测试线程对同一快照文件 read-modify-write 串行)。 +static WRITE_LOCK: Mutex<()> = Mutex::new(()); + +/// 对照(或 bless 时写回)`/generated/test_pins.json` 中的一个 pin。 +/// +/// `version_dir` = 该测试实际加载数据的版本目录(golden 测试传 +/// `GOLDEN_PARITY_DATA_VERSION` 对应目录,活动版本测试传 `DATA_VERSION` 对应 +/// 目录)——pin 跟数据同目录,regen 哪个版本就刷新哪个版本的快照。 +/// +/// 正常模式:pin 缺失或值不等 → panic,报错信息给出 bless 刷新命令。 +/// bless 模式(`POBR_BLESS_PINS=1`):写回实际值并通过。 +pub fn assert_pin(version_dir: &Path, name: &str, actual: impl Into) { + let actual = actual.into(); + let path = version_dir.join("generated/test_pins.json"); + + if std::env::var(BLESS_ENV).is_ok_and(|v| v == "1") { + let _guard = WRITE_LOCK.lock().unwrap(); + let mut pins = read_pins(&path); + pins.insert(name.to_string(), actual); + let mut json = serde_json::to_string_pretty(&pins).expect("serialize test pins"); + json.push('\n'); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).unwrap_or_else(|e| { + panic!("create {} failed: {e}", parent.display()); + }); + } + std::fs::write(&path, json) + .unwrap_or_else(|e| panic!("write {} failed: {e}", path.display())); + return; + } + + let pins = read_pins(&path); + match pins.get(name) { + Some(expected) if *expected == actual => {} + found => { + let expected = found.map_or("".to_string(), Value::to_string); + panic!( + "test pin `{name}` out of date in {}:\n blessed: {expected}\n actual: {actual}\n\ + 数据内容变了(regen 后属预期)。刷新:POBR_BLESS_PINS=1 重跑本测试\ + (pipeline/regen-all.sh 末步会批量刷新并提交快照)。", + path.display() + ); + } + } +} + +/// 读快照(缺文件 → 空 map;损坏文件 → panic,不静默)。 +fn read_pins(path: &Path) -> BTreeMap { + match std::fs::read(path) { + Ok(bytes) => serde_json::from_slice(&bytes) + .unwrap_or_else(|e| panic!("parse {} failed: {e}", path.display())), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => BTreeMap::new(), + Err(e) => panic!("read {} failed: {e}", path.display()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn temp_version_dir(tag: &str) -> std::path::PathBuf { + let dir = std::env::temp_dir().join(format!("pobr-test-pins-{tag}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + dir + } + + #[test] + fn bless_then_match_roundtrip() { + let dir = temp_version_dir("roundtrip"); + // ponytail: env var is process-global — this test only exercises the + // non-bless read path directly via a hand-written snapshot file. + std::fs::create_dir_all(dir.join("generated")).unwrap(); + std::fs::write( + dir.join("generated/test_pins.json"), + r#"{"a.count": 3, "b.obj": {"x": 2.5}}"#, + ) + .unwrap(); + assert_pin(&dir, "a.count", 3); + assert_pin(&dir, "b.obj", serde_json::json!({"x": 2.5})); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[should_panic(expected = "out of date")] + fn mismatch_panics_with_bless_hint() { + let dir = temp_version_dir("mismatch"); + std::fs::create_dir_all(dir.join("generated")).unwrap(); + std::fs::write(dir.join("generated/test_pins.json"), r#"{"a.count": 3}"#).unwrap(); + assert_pin(&dir, "a.count", 4); + } + + #[test] + #[should_panic(expected = "out of date")] + fn missing_pin_panics() { + let dir = temp_version_dir("missing"); + assert_pin(&dir, "no.such.pin", 1); + } +} From 13c990c220d794b7063ed01a120494ea7dcb9d96 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:18:52 +0800 Subject: [PATCH 04/74] test: migrate data-content count pins to blessed snapshot Move hand-pinned literals that mirror current data content into data/4.5.4.3/generated/test_pins.json: - parser_rules real_data_section_counts: all section lengths, distinct form count and unsupported lists -> parser_rules.section_counts pin; the structural guard (core form ids present) stays in code. - parser_rules handler_fallback_within_budget: the exact count (3) -> parser_rules.handler_fallbacks pin; the <=15 budget guard stays. - minions zombie/spectre coefficients (life/damage/armour, e.g. the 0.5.4b spectre life 3.0->2.2 rebalance) -> minions.* pins. Initial snapshot blessed from the current data; values match the previous literals exactly. --- crates/pobr-build/tests/skills/minions.rs | 16 +++- .../pobr-gamedata/src/domains/parser_rules.rs | 73 +++++++++++-------- data/4.5.4.3/generated/test_pins.json | 33 +++++++++ 3 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 data/4.5.4.3/generated/test_pins.json diff --git a/crates/pobr-build/tests/skills/minions.rs b/crates/pobr-build/tests/skills/minions.rs index 73438571..71eb3339 100644 --- a/crates/pobr-build/tests/skills/minions.rs +++ b/crates/pobr-build/tests/skills/minions.rs @@ -56,8 +56,12 @@ fn zombie_build(gem_level: u32) -> Build { fn build_data_minion_def_zombie() { let data = load_data(); let zombie = data.minion_def("RaisedZombie").expect("RaisedZombie 在库"); - assert_eq!(zombie.life, 0.7); // Minions.lua:12 - assert_eq!(zombie.damage, 0.75); // :18 + // 数值系数来自 Minions.lua,随平衡补丁漂移 → blessed 快照(POBR_BLESS_PINS=1 刷新)。 + pobr_gamedata::test_pins::assert_pin( + &repo_data_root().join(version()), + "minions.raised_zombie", + serde_json::json!({ "life": zombie.life, "damage": zombie.damage }), + ); assert!(zombie.base_damage_ignores_attack_speed); } @@ -68,8 +72,12 @@ fn build_data_minion_def_spectre_falls_back() { let c = data .minion_def("Metadata/Monsters/LeagueAbyss/Lightless/Cocoon3Spectre") .expect("Lightless Abomination 在库(落 spectres)"); - assert_eq!(c.life, 2.2); // Spectres.lua(4.5.4.3/0.5.4b:3.0→2.2) - assert_eq!(c.armour, 0.4); + // 数值系数来自 Spectres.lua,随平衡补丁漂移(0.5.4b:life 3.0→2.2)→ blessed 快照。 + pobr_gamedata::test_pins::assert_pin( + &repo_data_root().join(version()), + "minions.spectre_cocoon3", + serde_json::json!({ "life": c.life, "armour": c.armour }), + ); } #[test] diff --git a/crates/pobr-gamedata/src/domains/parser_rules.rs b/crates/pobr-gamedata/src/domains/parser_rules.rs index 089c85dc..6c6a7f0a 100644 --- a/crates/pobr-gamedata/src/domains/parser_rules.rs +++ b/crates/pobr-gamedata/src/domains/parser_rules.rs @@ -36,49 +36,53 @@ mod tests { use crate::GameData; + fn golden_version_dir() -> std::path::PathBuf { + crate::repo_data_root().join(pobr_data::GOLDEN_PARITY_DATA_VERSION) + } + fn real_data() -> GameData { // 本模块测试钉定段计数 / vendor-commit 实测样本(版本特定,随抽取增长),故加载 // golden 校验版本而非活动版本(数据/计算解耦,见 pobr_data::GOLDEN_PARITY_DATA_VERSION)。 - GameData::new(crate::repo_data_root().join(pobr_data::GOLDEN_PARITY_DATA_VERSION)) + GameData::new(golden_version_dir()) } - /// 仓库真实数据:各段条目数与钉定 vendor commit 的实测值一致 - /// (蓝图 §1.9 计数自检的消费侧镜像。golden 4.5.4.3(vendor ce8bffab)实测: - /// forms 91→95、name_map 775→789、flag_phrases 201→205、tag_phrases 687→691、 - /// flag_types 25→26、distinct forms 28→29)。 + /// 仓库真实数据:各段条目数与 blessed 快照一致(蓝图 §1.9 计数自检的消费侧 + /// 镜像)。计数随 vendor 抽取增长——具体数值进 `generated/test_pins.json` + /// (regen 后 `POBR_BLESS_PINS=1` 刷新,见 [`crate::test_pins`]);本函数只 + /// 保留结构性守卫(核心 form id 必须在场)。 #[test] fn real_data_section_counts() { let doc = real_data() .mod_parser_rules() .expect("加载 mod_parser_rules.json 不应失败") .expect("仓库数据包应含 mod_parser_rules 域"); - assert_eq!(doc.forms.len(), 95, "forms"); - assert_eq!(doc.name_map.len(), 789, "name_map"); - assert_eq!(doc.flag_phrases.len(), 205, "flag_phrases"); - assert_eq!(doc.pre_flags.len(), 219, "pre_flags"); - assert_eq!(doc.tag_phrases.len(), 691, "tag_phrases"); - assert_eq!(doc.suffix_types.len(), 40, "suffix_types"); - assert_eq!(doc.damage_types.len(), 5, "damage_types"); - assert_eq!(doc.pen_types.len(), 6, "pen_types"); - assert_eq!(doc.regen_types.len(), 32, "regen_types"); - assert_eq!(doc.degen_types.len(), 32, "degen_types"); - assert_eq!(doc.cost_types_map.len(), 32, "cost_types_map"); - assert_eq!(doc.base_cost_types.len(), 32, "base_cost_types"); - // 26 = vendor 25(4.5.4.3 ce8bffab 新增一条)+ pobr `hindered`→ - // `Condition:Hindered`(M6-conv2:legacy `parse_enemy_inner` 对 `are hindered` - // 的特例搬迁,使 `Enemies in your Presence are Hindered` 走 EnemyModifier - // 包装收敛,见 m6-dualrun-report §2.5)。 - assert_eq!(doc.flag_types.len(), 26, "flag_types"); - assert_eq!(doc.unsupported, vec!["mirrored"], "unsupported"); - assert_eq!( - doc.unsupported_pobr_extra, - vec!["split"], - "unsupported_pobr_extra" - ); - // form id 集 29 种(蓝图 §1.1 记 28;4.5.4.3 vendor 新增 1 种) let forms: std::collections::BTreeSet<&str> = doc.forms.iter().map(|f| f.form.as_str()).collect(); - assert_eq!(forms.len(), 29, "distinct form ids"); + // flag_types 含 pobr 自增条目 `hindered`→`Condition:Hindered`(M6-conv2, + // 见 m6-dualrun-report §2.5),计数恒 = vendor + 1。 + crate::test_pins::assert_pin( + &golden_version_dir(), + "parser_rules.section_counts", + serde_json::json!({ + "forms": doc.forms.len(), + "name_map": doc.name_map.len(), + "flag_phrases": doc.flag_phrases.len(), + "pre_flags": doc.pre_flags.len(), + "tag_phrases": doc.tag_phrases.len(), + "suffix_types": doc.suffix_types.len(), + "damage_types": doc.damage_types.len(), + "pen_types": doc.pen_types.len(), + "regen_types": doc.regen_types.len(), + "degen_types": doc.degen_types.len(), + "cost_types_map": doc.cost_types_map.len(), + "base_cost_types": doc.base_cost_types.len(), + "flag_types": doc.flag_types.len(), + "distinct_forms": forms.len(), + "unsupported": doc.unsupported, + "unsupported_pobr_extra": doc.unsupported_pobr_extra, + }), + ); + // 结构性守卫:核心 form id 缺失 = 抽取通道坏,不随数据漂移。 for id in [ "INC", "RED", "MORE", "LESS", "BASE", "PEN", "DMG", "DOUBLED", ] { @@ -240,7 +244,8 @@ mod tests { assert!(loaded.is_none()); } - /// 探针推断条目的 handler 兜底数量在蓝图预算内(≤15;当前实测 3)。 + /// 探针推断条目的 handler 兜底数量在蓝图预算内(≤15,结构性守卫);具体 + /// 条数随 vendor 漂移,进 blessed 快照。 #[test] fn handler_fallback_within_budget() { let doc = real_data().mod_parser_rules().unwrap().unwrap(); @@ -255,6 +260,10 @@ mod tests { .filter(|e| e.handler_id.is_some()) .count(); assert!(handlers <= 15, "handler 兜底 {handlers} 超出蓝图预算 ≤15"); - assert_eq!(handlers, 3, "钉定 vendor commit 下实测 3 条 handler 兜底"); + crate::test_pins::assert_pin( + &golden_version_dir(), + "parser_rules.handler_fallbacks", + handlers, + ); } } diff --git a/data/4.5.4.3/generated/test_pins.json b/data/4.5.4.3/generated/test_pins.json new file mode 100644 index 00000000..76527434 --- /dev/null +++ b/data/4.5.4.3/generated/test_pins.json @@ -0,0 +1,33 @@ +{ + "minions.raised_zombie": { + "damage": 0.75, + "life": 0.7 + }, + "minions.spectre_cocoon3": { + "armour": 0.4, + "life": 2.2 + }, + "parser_rules.handler_fallbacks": 3, + "parser_rules.section_counts": { + "base_cost_types": 32, + "cost_types_map": 32, + "damage_types": 5, + "degen_types": 32, + "distinct_forms": 29, + "flag_phrases": 205, + "flag_types": 26, + "forms": 95, + "name_map": 789, + "pen_types": 6, + "pre_flags": 219, + "regen_types": 32, + "suffix_types": 40, + "tag_phrases": 691, + "unsupported": [ + "mirrored" + ], + "unsupported_pobr_extra": [ + "split" + ] + } +} From be69bca037739863b27b4eb2b7d72e0b08811988 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:19:05 +0800 Subject: [PATCH 05/74] test: replace version-coupled assertions with version-stable guards Remaining assertions that broke (or would break) on data bumps, fixed without snapshots because their load-bearing meaning is version-stable: - pobr-core any-attribute: assert the invariant (no net contribution: zero mods or leftover, never a concrete attribute mod) instead of the version-specific shape (empty mods vs leftover). - precompile coverage baseline: the test asserted baseline == committed ratio, turning a deliberate ratchet into a pin that broke whenever coverage rose. Assert the ratchet direction instead (committed >= baseline, same semantics as regen-check.sh); raising the baseline stays a human decision, like parity_no_regression. - precompile --check repo test: derive the checked versions from pobr_data::{DATA_VERSION, GOLDEN_PARITY_DATA_VERSION} instead of a hand-pinned list (also stops sweeping stale data/ dirs). - wasm tier degradation test: keep the deliberate 4.5.0.3.4 legacy-pack fixture pin but skip gracefully if that pack is ever removed. --- apps/pobr-wasm/tests/tier_lines.rs | 9 +++++++-- crates/pobr-core/tests/parser/mod_parser.rs | 11 ++++++----- tools/precompile-mods/src/check.rs | 13 ++++++++++--- tools/precompile-mods/tests/precompile.rs | 16 ++++++++++++---- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/apps/pobr-wasm/tests/tier_lines.rs b/apps/pobr-wasm/tests/tier_lines.rs index 62cd4b59..c09fe440 100644 --- a/apps/pobr-wasm/tests/tier_lines.rs +++ b/apps/pobr-wasm/tests/tier_lines.rs @@ -46,11 +46,16 @@ fn current_data_annotates_explicit_tiers() { } /// 优雅降级:无 tier 池字段(group/spawn_weights)的数据包不标 tier。 -/// 显式钉 4.5.0.3.4——它早于 tier 数据通道,缺池字段;golden 版(4.5.4.3 起) -/// 已含池数据会标 tier,故此降级用例不能再借 `GOLDEN_PARITY_DATA_VERSION`。 +/// 显式钉 4.5.0.3.4——这是「早于 tier 数据通道的旧格式包」的 fixture 钉(非数据 +/// 内容计数钉,不进 test_pins 快照);golden 版(4.5.4.3 起)已含池数据会标 +/// tier,故此降级用例不能借 `GOLDEN_PARITY_DATA_VERSION`。旧包被清理时跳过。 #[test] fn golden_data_without_pool_fields_omits_tiers() { let dir = repo_data_root().join("4.5.0.3.4"); + if !dir.is_dir() { + eprintln!("SKIP: 旧格式 fixture 数据包 4.5.0.3.4 不在仓库(已清理)"); + return; + } let lines = classify(dir.to_str().unwrap()); assert!( lines.iter().all(|l| l.get("tier").is_none()), diff --git a/crates/pobr-core/tests/parser/mod_parser.rs b/crates/pobr-core/tests/parser/mod_parser.rs index b8171818..8e69a39c 100644 --- a/crates/pobr-core/tests/parser/mod_parser.rs +++ b/crates/pobr-core/tests/parser/mod_parser.rs @@ -249,13 +249,14 @@ fn strips_pob_bracket_markup() { assert_eq!(o.mods[0].name, ModName::from("ElementalDamage")); // `any attribute`(属性小点三选一)不展开——玩家选择经 AttributeOverride 在树 - // 收集阶段改写为具体属性后再解析。4.5.4.3 vendor specialModList 起,原文 - // `to any attribute` 被 vendor 规则整行识别为「无贡献」(空 mods),与旧引擎 - // 「留残 → 生产闸门整行丢弃」净效果一致:未选中节点绝不产出任何具体属性 mod。 + // 收集阶段改写为具体属性后再解析。「无净贡献」的具体形态随数据版本变:旧规则 + // 留残(生产闸门整行丢弃),4.5.4.3 vendor 规则整行识别为空 mods。两者净效果 + // 一致,故只断言版本无关不变式:要么零 mod,要么留残,绝不产出具体属性 mod。 let o = parse_mod("+5 to any [Attributes|Attribute]").unwrap(); assert!( - o.mods.is_empty(), - "any attribute 原文不产出任何 mod(选择在树收集阶段改写)" + o.mods.is_empty() || o.unparsed.is_some(), + "any attribute 原文不得产出净贡献(零 mod 或留残丢弃):{:?}", + o.mods ); for attr in ["Strength", "Dexterity", "Intelligence"] { assert!( diff --git a/tools/precompile-mods/src/check.rs b/tools/precompile-mods/src/check.rs index 5f5e0ede..f8b6bb25 100644 --- a/tools/precompile-mods/src/check.rs +++ b/tools/precompile-mods/src/check.rs @@ -128,11 +128,18 @@ fn load_strict(path: &Path, errors: &mut Vec) -> Op mod tests { use super::*; - /// The committed repo data passes `--check` (deserialize + compile clean), - /// for both the parity-golden version and CURRENT. + /// The committed repo data passes `--check` (deserialize + compile clean) + /// for the active and parity-golden versions. Versions come from the + /// `pobr_data` constants (auto-advance on data bumps, no literal to + /// re-pin); stale/experimental dirs under `data/` are out of contract. #[test] fn repo_overlay_passes_check() { - for version in ["4.5.0.3.4", "4.5.4.3"] { + let mut versions = vec![ + pobr_data::DATA_VERSION, + pobr_data::GOLDEN_PARITY_DATA_VERSION, + ]; + versions.dedup(); + for version in versions { let data_dir = Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../data") .join(version); diff --git a/tools/precompile-mods/tests/precompile.rs b/tools/precompile-mods/tests/precompile.rs index 76427852..80f7866e 100644 --- a/tools/precompile-mods/tests/precompile.rs +++ b/tools/precompile-mods/tests/precompile.rs @@ -125,14 +125,22 @@ fn committed_coverage_matches_fresh_run() { 请重跑 cargo run -p precompile-mods -- --data data/{PATCH} --report 并提交" ); - // 棘轮基线应与已提交产物的 coverage_ratio 一致。 + // 覆盖率棘轮:已提交产物不得低于基线(与 devs/scripts/regen-check.sh 同语义)。 + // 基线是人工决策闸门(同 parity_no_regression),不随 regen 自动刷新;覆盖率 + // 提升后抬高基线是可选的 deliberate 动作,故这里断言方向而非相等——数据 regen + // 抬升覆盖率不再机械打碎本测试。 let baseline_path = repo_root().join("devs/ci/parse-coverage-baseline.json"); if baseline_path.is_file() { let baseline: serde_json::Value = serde_json::from_slice(&std::fs::read(&baseline_path).unwrap()).unwrap(); - assert_eq!( - baseline["coverage_ratio"], committed_summary["coverage_ratio"], - "baseline coverage_ratio 与已提交报表不一致——更新 devs/ci/parse-coverage-baseline.json" + let base = baseline["coverage_ratio"].as_f64().expect("baseline ratio"); + let cur = committed_summary["coverage_ratio"] + .as_f64() + .expect("committed ratio"); + assert!( + cur + 5e-7 >= base, + "覆盖率棘轮失败:已提交 {cur} < 基线 {base}——解析覆盖率不得降低;\ + 若属预期(语料扩面)请同 PR 更新 devs/ci/parse-coverage-baseline.json" ); } From 1cecfbdd51eb9cbd583255653afcc08153a95d75 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:19:16 +0800 Subject: [PATCH 06/74] chore(pipeline): bless test-pin snapshot as regen-all final step Run the migrated pin tests with POBR_BLESS_PINS=1 at the end of regen-all.sh so a data regen refreshes generated/test_pins.json in the same pass (soft_step: failures are recorded, not fatal). Uses cargo test (single process; the helper serializes writes in-process) rather than nextest, which runs one process per test and would race on the snapshot file. --- pipeline/regen-all.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pipeline/regen-all.sh b/pipeline/regen-all.sh index f2775396..3ebf222d 100755 --- a/pipeline/regen-all.sh +++ b/pipeline/regen-all.sh @@ -179,15 +179,28 @@ mkdir -p "$OUT_DIR/generated" soft_step special_vendor "${SYNC[@]}" extract-lua --what special-mods --vendor-root "$VENDOR" --out "$OUT_DIR/generated/special_vendor.json" # ---- 7) generated/(precompile-mods)---- -echo "== [7/8] precompile-mods(generated/)" +echo "== [7/9] precompile-mods(generated/)" soft_step precompile_mods cargo run --quiet -p precompile-mods -- --data "$OUT_DIR" --report # ---- 8) manifest.json:以 OLD_PATCH 结构为模板,仅换版本号 ---- -echo "== [8/8] manifest.json" +echo "== [8/9] manifest.json" if [[ -f "data/$OLD_PATCH/manifest.json" ]]; then - sed "s/\"$OLD_PATCH\"/\"$PATCH\"/g" "data/$OLD_PATCH/manifest.json" > "$OUT_DIR/manifest.json" + sed "s/\"${OLD_PATCH}\"/\"${PATCH}\"/g" "data/${OLD_PATCH}/manifest.json" > "${OUT_DIR}/manifest.json" fi +# ---- 9) test-pin 快照 bless(generated/test_pins.json,v0.0.3 P0-1)---- +# 数据内容计数钉(parser 规则段计数 / minion 系数等)不再手写在测试里,而是存 +# 每版本一份的 generated/test_pins.json;这里以 POBR_BLESS_PINS=1 重跑对应定向 +# 测试把实际值写回快照(与 regen 同一提交)。注意: +# - 快照落在各测试**实际加载**的版本目录(golden 测试 = +# pobr_data::GOLDEN_PARITY_DATA_VERSION)——升级 drill 中 golden 尚未切换时, +# 刷新的是旧 golden 目录,属预期; +# - 必须用 cargo test(单进程多线程,写回有进程内锁),勿换 nextest +# (进程/测试并发写同一快照会互相覆盖)。 +echo "== [9/9] test-pin bless(generated/test_pins.json)" +soft_step pin_parser_rules env POBR_BLESS_PINS=1 cargo test --quiet -p pobr-gamedata --lib parser_rules +soft_step pin_minions env POBR_BLESS_PINS=1 cargo test --quiet -p pobr-build --test skills minions::build_data_minion_def + # ---- 汇总 ---- echo "regen-all: 完成 → $OUT_DIR" if [[ ${#OVERLAY_FAILURES[@]} -gt 0 ]]; then From c806772c63701a2e3bde6c5a586fafa8e6aac08f Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:25:58 +0800 Subject: [PATCH 07/74] feat(pipeline): one-command version bump orchestrator (P1-4) bump-version.sh chains the whole drill that previously lived in memory notes: patch query, .dat download, tree/vendor alignment, regen-all (which now blesses test pins), CURRENT + DATA_VERSION advance, zh-CN and web data sync, then a targeted verification pass. Golden flip, vendor pin choice, and engine adaptation stay explicit manual decisions printed in the final summary. --- pipeline/README.md | 4 +- pipeline/bump-version.sh | 176 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) create mode 100755 pipeline/bump-version.sh diff --git a/pipeline/README.md b/pipeline/README.md index 45681a7c..d42b37f6 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -44,7 +44,9 @@ cargo run -p pobr-data-adapter -- --raw ./tables --out ../data --patch ## 扩展 / 升版 -- 新 PoE2 版本:更新 `config.json` 的 `patch`,重跑三步,`data/` 下生成新版本目录,`diff` 审查。 +- 新 PoE2 版本:**`pipeline/bump-version.sh` 一条命令**(查补丁号 → 下载 → 树/vendor 对齐 → + regen-all(含 test-pin bless)→ 推进 CURRENT/DATA_VERSION → zh-CN/web 同步 → 定向验证), + 末尾打印剩余人工决策(golden 翻转、引擎 delta triage)。分步等价操作见脚本头注释。 - 新数据域:在 `config.json` 的 `tables` 增表/列,并在 `pobr-data-adapter` 增对应适配器。 - **CDN 只保留当前补丁**:GGG patch CDN 会下线旧版本(M1-W0 时 4.5.0.3.4 已 404)。`.cache/` 里已缓存的 bundle 可继续离线导出**既有表的全部列**(整张 `.datc64` 在同一 bundle 里); diff --git a/pipeline/bump-version.sh b/pipeline/bump-version.sh new file mode 100755 index 00000000..7a8fafbb --- /dev/null +++ b/pipeline/bump-version.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# bump-version.sh —— 游戏数据版本升级一条命令编排(v0.0.3 P1-4,docs/version-bump-architecture.md)。 +# +# 把此前散在 memory/README 里的升级 drill 串成单入口: +# [1] 查询最新补丁号(query-patch-version.mjs),改 pipeline/config.json "patch" +# [2] 下载 .dat 表(download-index.mjs 预热索引 + npx pathofexile-dat) +# [3] 刷新被动树导出(GGG poe2-skilltree-export;官方停更时沿用现有,软失败) +# [4] vendor 对齐(--vendor-sha 时 fetch-by-sha 换检出 + 更新 .pob2-version.txt) +# [5] OLD_PATCH=<旧> regen-all.sh(含末步 test-pin bless) +# [6] 推进版本标记:data/CURRENT + pobr-data DATA_VERSION 常量 +# [7] gen-zh-cn.mjs(读 data/CURRENT)+ web pnpm sync-data +# [8] 定向验证(多版本 smoke + gamedata 套件;parity 仅报告不判失败) +# [9] 摘要 + 剩余人工决策清单 +# +# 刻意保留为人工决策(不自动化): +# - golden 翻转(GOLDEN_PARITY_DATA_VERSION + recapture_golden.py)——是否把 +# parity 基准挪到新版本取决于引擎适配进度,见 docs/adapting-to-0.5.4b.md 的教训; +# - vendor 新 pin 的选取(PoB2 社区哪个 commit 对应新补丁); +# - 引擎公式适配本身(先跑 pipeline/diff-vendor-calcs.sh 拿 delta 清单)。 +# +# 用法: +# pipeline/bump-version.sh # 补丁号自动查询,vendor 不动 +# pipeline/bump-version.sh --patch 4.5.5.1 # 显式补丁号 +# pipeline/bump-version.sh --vendor-sha <全长sha> # 同时换 vendor 检出 +# pipeline/bump-version.sh --skip-download # 表已下好,从 regen 开始 +# +# 各步幂等(下载有缓存、regen 整目录覆写),失败后修好直接重跑即可。 + +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT}" || exit 1 + +NEW_PATCH="" +VENDOR_SHA="" +SKIP_DOWNLOAD=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --patch) NEW_PATCH="$2"; shift 2 ;; + --vendor-sha) VENDOR_SHA="$2"; shift 2 ;; + --skip-download) SKIP_DOWNLOAD=1; shift ;; + *) echo "bump-version: 未知参数 $1" >&2; exit 2 ;; + esac +done + +FAILURES=() +soft_step() { + local label="$1"; shift + echo "---- ${label}" + if ! "$@"; then + FAILURES+=("${label}") + echo " ⚠ 软步骤失败(续跑其余):${label}" >&2 + fi +} +die_on_fail() { "$@" || { echo "bump-version: 关键步骤失败,中止:$*" >&2; exit 1; }; } + +OLD_PATCH="$(sed -n 's/.*"patch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' pipeline/config.json | head -1)" +[[ -n "${OLD_PATCH}" ]] || { echo "bump-version: 无法从 config.json 读取当前 patch" >&2; exit 1; } + +# ---- [1] 目标补丁号 ---- +echo "== [1/9] 目标补丁号" +if [[ -z "${NEW_PATCH}" ]]; then + NEW_PATCH="$(node pipeline/query-patch-version.mjs | sed -n 's/.*"version": "\([^"]*\)".*/\1/p')" + [[ -n "${NEW_PATCH}" ]] || { echo "bump-version: patch 服务器查询失败,用 --patch 显式指定" >&2; exit 1; } +fi +echo " ${OLD_PATCH} → ${NEW_PATCH}" +if [[ "${NEW_PATCH}" == "${OLD_PATCH}" ]]; then + echo " 已是最新(同版本重放请用 devs/scripts/version-bump-drill.sh)——继续执行属刷新语义" +fi +# 只改 "patch" 一行;config.json 其余(表清单)人工维护。sed -i 的 in-place 语法 +# BSD/GNU 不兼容,统一走 tmp+mv。 +sed_replace() { + local pattern="$1" file="$2" tmp + tmp="$(mktemp)" + sed "${pattern}" "${file}" > "${tmp}" && mv "${tmp}" "${file}" +} +die_on_fail sed_replace "s/\"patch\": \"${OLD_PATCH}\"/\"patch\": \"${NEW_PATCH}\"/" pipeline/config.json + +# ---- [2] .dat 表下载 ---- +echo "== [2/9] GGG .dat 表下载(pipeline/tables/)" +if [[ "${SKIP_DOWNLOAD}" -eq 1 ]]; then + echo " --skip-download:跳过" +else + # CDN 只保留当前补丁(pipeline/README.md)——下载失败通常意味着补丁号过期,重查步骤 1。 + die_on_fail bash -c "cd pipeline && node download-index.mjs" + die_on_fail bash -c "cd pipeline && npx -y pathofexile-dat@15" +fi + +# ---- [3] 被动树导出 ---- +echo "== [3/9] 被动树导出(GGG poe2-skilltree-export)" +# 官方树导出常滞后于补丁(0.5.4b 时仍停在 0.5.2)——拿不到就沿用现有 data.json。 +TREE_URL="https://raw.githubusercontent.com/grindinggear/poe2-skilltree-export/HEAD/data.json" +fetch_tree() { + local tmp; tmp="$(mktemp)" + if curl -fsSL --retry 3 -o "${tmp}" "${TREE_URL}" && [[ -s "${tmp}" ]]; then + if cmp -s "${tmp}" pipeline/tree/data.json; then + echo " 树导出无变化" + else + mv "${tmp}" pipeline/tree/data.json + echo " 树导出已更新" + fi + else + rm -f "${tmp}" + echo " 下载失败——沿用现有 pipeline/tree/data.json(官方导出滞后属常态)" + fi +} +soft_step tree_export fetch_tree + +# ---- [4] vendor 对齐 ---- +echo "== [4/9] PoB2 vendor 对齐" +VENDOR_DIR="vendor/PathOfBuilding-PoE2" +OLD_VENDOR_SHA="$(cat vendor/.pob2-version.txt 2>/dev/null || echo '')" +if [[ -n "${VENDOR_SHA}" ]]; then + [[ "${#VENDOR_SHA}" -eq 40 ]] || { echo "bump-version: --vendor-sha 需全长 40 位(gh api 查,别猜短 sha)" >&2; exit 1; } + swap_vendor() { + mkdir -p "${VENDOR_DIR}" + git -C "${VENDOR_DIR}" rev-parse --git-dir >/dev/null 2>&1 || git -C "${VENDOR_DIR}" init -q + git -C "${VENDOR_DIR}" remote get-url origin >/dev/null 2>&1 \ + || git -C "${VENDOR_DIR}" remote add origin https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2 + git -C "${VENDOR_DIR}" fetch -q --depth 1 origin "${VENDOR_SHA}" \ + && git -C "${VENDOR_DIR}" checkout -q FETCH_HEAD \ + && printf '%s\n' "${VENDOR_SHA}" > vendor/.pob2-version.txt + } + die_on_fail swap_vendor + echo " vendor → ${VENDOR_SHA}" +else + echo " 未指定 --vendor-sha:vendor 保持 ${OLD_VENDOR_SHA:-<未检出>}(overlay 抽取将基于旧 vendor)" +fi + +# ---- [5] 全量重生成(含 test-pin bless 末步)---- +echo "== [5/9] regen-all(OLD_PATCH=${OLD_PATCH})" +die_on_fail env OLD_PATCH="${OLD_PATCH}" pipeline/regen-all.sh + +# ---- [6] 推进活动版本标记 ---- +echo "== [6/9] data/CURRENT + pobr_data::DATA_VERSION" +printf '%s\n' "${NEW_PATCH}" > data/CURRENT +# 只动 DATA_VERSION 一行;GOLDEN_PARITY_DATA_VERSION 是人工决策,绝不自动推进。 +die_on_fail sed_replace \ + "s/^pub const DATA_VERSION: &str = \"${OLD_PATCH}\";/pub const DATA_VERSION: \&str = \"${NEW_PATCH}\";/" \ + crates/pobr-data/src/lib.rs +grep -q "pub const DATA_VERSION: &str = \"${NEW_PATCH}\";" crates/pobr-data/src/lib.rs \ + || { echo "bump-version: DATA_VERSION 常量替换失败(crates/pobr-data/src/lib.rs 格式变了?)" >&2; exit 1; } + +# ---- [7] 衍生数据 ---- +echo "== [7/9] 简中语言包 + web 数据同步" +soft_step gen_zh_cn node pipeline/gen-zh-cn.mjs +if [[ -d web/node_modules ]]; then + soft_step web_sync_data bash -c "cd web && pnpm run sync-data" +else + echo " web/node_modules 缺失——跳过 sync-data(web 下次 pnpm install 后手动跑)" + FAILURES+=("web sync-data 未跑(无 node_modules)") +fi + +# ---- [8] 定向验证(禁全量,见 CLAUDE.md 验证分层)---- +echo "== [8/9] 定向验证" +soft_step smoke_versions bash .claude/skills/run-pobr/driver.sh versions +soft_step gamedata_tests env CARGO_BUILD_RUSTC_WRAPPER="" cargo test --quiet -p pobr-gamedata +# parity 只报告不判失败:golden 仍钉旧版本时本就不应红;红/绿都由人读。 +echo "---- parity(信息性,不计失败)" +env CARGO_BUILD_RUSTC_WRAPPER="" cargo test --quiet -p pobr-build --test parity 2>&1 | tail -5 || true + +# ---- [9] 摘要 ---- +echo "== [9/9] 摘要" +echo "补丁:${OLD_PATCH} → ${NEW_PATCH} vendor:${OLD_VENDOR_SHA:-?} → $(cat vendor/.pob2-version.txt 2>/dev/null || echo '?')" +if [[ ${#FAILURES[@]} -gt 0 ]]; then + echo "软失败步骤(须处理后重跑对应步):" + printf ' - %s\n' "${FAILURES[@]}" +fi +cat <} <新sha> 拿公式 delta 清单; + 3. golden 是否翻到 ${NEW_PATCH}:引擎补齐后跑 examples/demo-bd-test/tools/recapture_golden.py + 并推进 pobr_data::GOLDEN_PARITY_DATA_VERSION(教训见 docs/adapting-to-0.5.4b.md)。 +EOF +[[ ${#FAILURES[@]} -eq 0 ]] From 1d299005f82009b3cdb73441a9df5ae2a1dd4468 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:30:16 +0800 Subject: [PATCH 08/74] feat(gamedata): merge version-independent overlay-common special_mods layer Load special_mods.json in two layers: version-independent curated data/overlay-common/special_mods.json merged under version-specific data//overlay/special_mods.json. Version entries override common by id, others append; both absent yields None (unchanged soft-degrade). Adds overlay_common_path() helper (sibling of the version dir; resolves to overlay-common/ for the in-memory wasm backend). Covers inherit / override-by-id / common-only / both-absent behaviors in load_special_mods. Part of P1-3 (docs/version-bump-architecture.md): curated entries carry forward to new data versions without manual per-version copying. --- .../pobr-gamedata/src/domains/special_mods.rs | 46 +++++++++- crates/pobr-gamedata/src/paths.rs | 15 ++++ .../tests/mods/load_special_mods.rs | 87 +++++++++++++++++++ 3 files changed, 144 insertions(+), 4 deletions(-) diff --git a/crates/pobr-gamedata/src/domains/special_mods.rs b/crates/pobr-gamedata/src/domains/special_mods.rs index b22e5569..450d98e0 100644 --- a/crates/pobr-gamedata/src/domains/special_mods.rs +++ b/crates/pobr-gamedata/src/domains/special_mods.rs @@ -12,11 +12,35 @@ use pobr_data::catalog::parser_rules::SpecialModsDef; use crate::{GameData, LoadError}; impl GameData { - /// 加载 special 词条模板表(恒走 `overlay/` 定位)。文件缺失返回 - /// `Ok(None)`(M5b B-4 约定:缺表 → RuleSet 域 None,解析行为退回 - /// 既有硬编码路径);其余错误照常上抛。 + /// 加载 special 词条模板表,**两层合并**:先读版本无关策展层 + /// `data/overlay-common/special_mods.json`(不存在则跳过),再叠版本层 + /// `/overlay/special_mods.json`。合并语义简单明确:**版本层条目按 `id` + /// 覆盖 common 层同 id 条目,其余按出现序追加**(common 顺序保持,version-only + /// 追加在后)。人工策展的 vendor-语义修正放 common 层,新数据版本目录自动继承, + /// 免去逐版本手迁(`docs/version-bump-architecture.md` P1-3)。 + /// + /// 两层皆缺 → `Ok(None)`(M5b B-4 约定:缺表 → RuleSet 域 None,解析退回硬编码 + /// 路径)。任一层坏 JSON 照常上抛;common 层文件缺失是常态(软降级,非错误)。 pub fn special_mods(&self) -> Result, LoadError> { - match self.load_json_at::(self.overlay_path("special_mods.json")) { + let common = match self.overlay_common_path("special_mods.json") { + Some(path) => self.load_special_mods_at(path)?, + None => None, + }; + let version = self.load_special_mods_at(self.overlay_path("special_mods.json"))?; + Ok(match (common, version) { + (None, None) => None, + (Some(def), None) | (None, Some(def)) => Some(def), + (Some(common), Some(version)) => Some(merge_special_layers(common, version)), + }) + } + + /// 读一个 `special_mods` schema 文件为 `Option`:NotFound → `None`(软降级), + /// 其余错误上抛。`load_json_at` 仍叠加用户 patch 层。 + fn load_special_mods_at( + &self, + path: std::path::PathBuf, + ) -> Result, LoadError> { + match self.load_json_at::(path) { Ok(def) => Ok(Some(def)), Err(LoadError::Io { ref source, .. }) if source.kind() == std::io::ErrorKind::NotFound => @@ -57,3 +81,17 @@ impl GameData { } } } + +/// 合并策展两层:`common`(版本无关基底)叠 `version`(版本特有覆盖)。version 的每条 +/// 按 `id` 覆盖 common 同 id 条目(整条替换,保持 common 原位);common 无此 id 的 +/// version 条目按 version 出现序追加在末尾。同输入恒同输出(确定性)。 +fn merge_special_layers(common: SpecialModsDef, version: SpecialModsDef) -> SpecialModsDef { + let mut entries = common.entries; + for v in version.entries { + match entries.iter_mut().find(|e| e.id == v.id) { + Some(slot) => *slot = v, + None => entries.push(v), + } + } + SpecialModsDef { entries } +} diff --git a/crates/pobr-gamedata/src/paths.rs b/crates/pobr-gamedata/src/paths.rs index 537b0f03..901891f1 100644 --- a/crates/pobr-gamedata/src/paths.rs +++ b/crates/pobr-gamedata/src/paths.rs @@ -39,6 +39,21 @@ impl GameData { pub(crate) fn generated_path(&self, rel: &str) -> PathBuf { self.root().join("generated").join(rel) } + + /// 定位 **版本无关策展层** overlay 文件:`data/overlay-common/`——版本目录的 + /// **同级** `overlay-common/` 兄弟目录(`/../overlay-common/`)。 + /// + /// 人工策展、随游戏版本不变的 vendor-语义修正放这里,新数据版本目录自动继承,免去 + /// 逐版本手迁(见 `docs/version-bump-architecture.md` P1-3)。加载侧把它 merge 到 + /// 版本层 `overlay/` **之下**(版本层按条目 key 覆盖,其余追加)。 + /// + /// 返回 `None` 仅当版本根无父目录(如 root 为文件系统根)——正常磁盘/内存后端恒 + /// `Some`:内存后端 root=``,父为空 → 键规约为 `overlay-common/`。 + pub(crate) fn overlay_common_path(&self, rel: &str) -> Option { + self.root() + .parent() + .map(|parent| parent.join("overlay-common").join(rel)) + } } #[cfg(test)] diff --git a/crates/pobr-gamedata/tests/mods/load_special_mods.rs b/crates/pobr-gamedata/tests/mods/load_special_mods.rs index aedb3de3..843bcef4 100644 --- a/crates/pobr-gamedata/tests/mods/load_special_mods.rs +++ b/crates/pobr-gamedata/tests/mods/load_special_mods.rs @@ -168,3 +168,90 @@ fn no_open_captures_in_patterns() { ); } } + +// ===== 版本无关策展层(data/overlay-common/)两层合并行为(P1-3)===== +// +// 在临时目录里手搭两层,精确验证合并语义(repo 真数据两层 id 互斥,无 override 样本): +// /overlay-common/special_mods.json ← 版本无关基底 +// //overlay/special_mods.json ← 版本特有覆盖 + +/// 建唯一临时 data 根,返回 (根, 版本目录)。 +fn temp_data_root(tag: &str) -> (std::path::PathBuf, std::path::PathBuf) { + let root = std::env::temp_dir().join(format!( + "pobr-gamedata-special-merge-{tag}-{}", + std::process::id() + )); + let _ = std::fs::remove_dir_all(&root); + let ver_dir = root.join("4.5.4.3"); + std::fs::create_dir_all(ver_dir.join("overlay")).unwrap(); + (root, ver_dir) +} + +/// 写一个只含给定 id 条目的 special_mods.json(最小合法形状)。 +fn write_special(path: &std::path::Path, entries: &[(&str, &str)]) { + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + let body: Vec = entries + .iter() + .map(|(id, pattern)| format!(r#"{{"id":"{id}","pattern":"{pattern}","batch":"T"}}"#)) + .collect(); + std::fs::write(path, format!(r#"{{"entries":[{}]}}"#, body.join(","))).unwrap(); +} + +fn ids(def: &SpecialModsDef) -> Vec { + def.entries.iter().map(|e| e.id.clone()).collect() +} + +/// common 层被继承,且 version-only 条目追加在后(common 顺序在前)。 +#[test] +fn overlay_common_is_inherited() { + let (root, ver_dir) = temp_data_root("inherit"); + write_special( + &root.join("overlay-common/special_mods.json"), + &[("common_a", "aaa"), ("common_b", "bbb")], + ); + write_special( + &ver_dir.join("overlay/special_mods.json"), + &[("ver_c", "ccc")], + ); + let def = GameData::new(&ver_dir).special_mods().unwrap().unwrap(); + assert_eq!(ids(&def), ["common_a", "common_b", "ver_c"]); +} + +/// version 层同 id 整条覆盖 common 层(不产生重复 id)。 +#[test] +fn version_layer_overrides_common_by_id() { + let (root, ver_dir) = temp_data_root("override"); + write_special( + &root.join("overlay-common/special_mods.json"), + &[("shared", "old_pattern"), ("common_only", "keep")], + ); + write_special( + &ver_dir.join("overlay/special_mods.json"), + &[("shared", "new_pattern")], + ); + let def = GameData::new(&ver_dir).special_mods().unwrap().unwrap(); + // 覆盖后仍只有一条 shared,取 version 的 pattern;common_only 保留。 + assert_eq!(ids(&def), ["shared", "common_only"]); + let shared = def.entries.iter().find(|e| e.id == "shared").unwrap(); + assert_eq!(shared.pattern, "new_pattern"); +} + +/// 仅 common 存在(版本目录无 overlay/special_mods.json)→ 直接继承 common。 +#[test] +fn common_only_when_version_absent() { + let (root, ver_dir) = temp_data_root("common-only"); + write_special( + &root.join("overlay-common/special_mods.json"), + &[("common_x", "xxx")], + ); + // 版本层文件缺失。 + let def = GameData::new(&ver_dir).special_mods().unwrap().unwrap(); + assert_eq!(ids(&def), ["common_x"]); +} + +/// 两层皆缺 → None(保留 RuleSet 域缺表软降级语义)。 +#[test] +fn both_layers_absent_yields_none() { + let (_root, ver_dir) = temp_data_root("none"); + assert!(GameData::new(&ver_dir).special_mods().unwrap().is_none()); +} From b2dfdb3d8bbb832399869c1fed55a152016dee71 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:30:29 +0800 Subject: [PATCH 09/74] chore: teach every special_mods reader the overlay-common layer special_mods is read in several places outside the gamedata loader; each must see the version-independent overlay-common layer so the migration is behavior-preserving: - precompile-mods check.rs: prepend overlay-common in the compile set; precompile.rs test harness mirrors overlay-common into the isolated tmp (its GameData-based coverage recompute resolves the sibling layer there). - sync-pob-catalog extract dedup + special_mods_patterns test: read overlay-common so re-extraction dedups against curated entries and pattern validation covers them. - pobr-core parser support + special_mods_gate + repo compile smoke test: merge overlay-common under the version layer (same id-override semantics as the loader) since pobr-core cannot depend on pobr-gamedata. - web sync-data.mjs: copy data/overlay-common into the web bundle and list it in the manifest so the wasm in-memory backend inherits it. - regen-all.sh: drop special_mods from the per-version carry-forward list; overlay-common replaces that manual step. - docs/contributing-mods.md: document overlay-common as where curated entries go. Part of P1-3 (docs/version-bump-architecture.md). --- crates/pobr-core/src/rules/special_mod.rs | 30 +++++++++---- .../tests/parser/special_mods_gate.rs | 27 +++++++++--- crates/pobr-core/tests/support/parse.rs | 11 ++++- docs/contributing-mods.md | 21 ++++++++- pipeline/regen-all.sh | 18 +++++--- tools/precompile-mods/src/check.rs | 18 +++++--- tools/precompile-mods/tests/precompile.rs | 15 ++++++- .../src/extract_special_mods.rs | 18 +++++--- .../tests/extract/special_mods_patterns.rs | 44 ++++++++++++++----- web/scripts/sync-data.mjs | 13 ++++++ 10 files changed, 172 insertions(+), 43 deletions(-) diff --git a/crates/pobr-core/src/rules/special_mod.rs b/crates/pobr-core/src/rules/special_mod.rs index 82e890a0..986e3097 100644 --- a/crates/pobr-core/src/rules/special_mod.rs +++ b/crates/pobr-core/src/rules/special_mod.rs @@ -1415,16 +1415,30 @@ mod tests { } /// 真实仓库数据全量编译成功(闸门冒烟,正式断言在 special_mods_gate.rs)。 + /// special_mods 分两层:版本无关策展层 `data/overlay-common/`(P1-3)+ 版本层 + /// `data//overlay/`。这里读同一并集拼接后编译(pobr-core 不依赖 pobr-gamedata, + /// 故直读文件而非走 loader)。 #[test] fn repo_special_mods_compile() { - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../../data") - .join(pobr_data::data_version()) - .join("overlay/special_mods.json"); - let raw = std::fs::read_to_string(path).expect("special_mods.json 可读"); - let doc: SpecialModsDef = serde_json::from_str(&raw).expect("special_mods.json 可解析"); - let rules = SpecialModRules::compile(&doc.entries, &HandlerRegistry::new()) + let data_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data"); + let mut entries = Vec::new(); + for path in [ + data_root.join("overlay-common/special_mods.json"), + data_root + .join(pobr_data::data_version()) + .join("overlay/special_mods.json"), + ] { + let raw = match std::fs::read_to_string(&path) { + Ok(raw) => raw, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue, + Err(e) => panic!("{}: {e}", path.display()), + }; + let doc: SpecialModsDef = serde_json::from_str(&raw).expect("special_mods.json 可解析"); + entries.extend(doc.entries); + } + assert!(!entries.is_empty(), "special_mods 两层皆空?"); + let rules = SpecialModRules::compile(&entries, &HandlerRegistry::new()) .expect("仓库 special_mods 全量编译成功"); - assert_eq!(rules.len(), doc.entries.len()); + assert_eq!(rules.len(), entries.len()); } } diff --git a/crates/pobr-core/tests/parser/special_mods_gate.rs b/crates/pobr-core/tests/parser/special_mods_gate.rs index f716ec52..f38224bb 100644 --- a/crates/pobr-core/tests/parser/special_mods_gate.rs +++ b/crates/pobr-core/tests/parser/special_mods_gate.rs @@ -1,7 +1,7 @@ //! special_mods 闸门测试(M5b 蓝图 C-4,§0.3 监控线落成 CI 原生门禁)。 //! -//! 读仓库 `data/4.5.0.3.4/{overlay/special_mods.json, generated/special_derived.json}`, -//! 断言: +//! 读仓库 `data/overlay-common/special_mods.json`(版本无关策展层,P1-3)+ +//! `data//{overlay/special_mods.json, generated/special_derived.json}`,断言: //! 1. [`SpecialModRules::compile`] 全量成功(pattern 合法 / mod_type 已知 / //! enums 引用不越界 / id 唯一); //! 2. 所有 `handler_id` 均已注册(未注册 = 测试失败 + 打印未映射清单, @@ -18,6 +18,11 @@ use std::collections::BTreeMap; use pobr_core::rules::{HandlerRegistry, SpecialModRules}; use pobr_data::catalog::parser_rules::{SpecialModsDef, SpecialTemplateDef}; +fn overlay_common_special_mods_path() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../data") + .join("overlay-common/special_mods.json") +} fn special_mods_path() -> std::path::PathBuf { std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../data") @@ -37,12 +42,24 @@ fn special_vendor_path() -> std::path::PathBuf { .join("generated/special_vendor.json") } -/// 加载仓库 special 条目(overlay + 可选 generated 派生/vendor 批量,拼接——与 -/// pobr-gamedata `load_ruleset` 三源同序)。 +/// 加载仓库 special 条目(overlay-common 版本无关层 + 版本 overlay + 可选 generated +/// 派生/vendor 批量,拼接——与 pobr-gamedata `load_ruleset` 同序)。overlay-common 层 +/// (P1-3)按 id 打底,版本层覆盖 / 追加。 fn load_entries() -> Vec { + let mut entries: Vec = Vec::new(); + if let Ok(raw) = std::fs::read_to_string(overlay_common_special_mods_path()) { + let doc: SpecialModsDef = + serde_json::from_str(&raw).expect("overlay-common/special_mods.json 可解析"); + entries = doc.entries; + } let raw = std::fs::read_to_string(special_mods_path()).expect("special_mods.json 可读"); let doc: SpecialModsDef = serde_json::from_str(&raw).expect("special_mods.json 可解析"); - let mut entries = doc.entries; + for v in doc.entries { + match entries.iter_mut().find(|e| e.id == v.id) { + Some(slot) => *slot = v, + None => entries.push(v), + } + } if let Ok(raw) = std::fs::read_to_string(special_derived_path()) { let derived: SpecialModsDef = serde_json::from_str(&raw).expect("special_derived.json 可解析"); diff --git a/crates/pobr-core/tests/support/parse.rs b/crates/pobr-core/tests/support/parse.rs index 7beaf4bd..c52967b1 100644 --- a/crates/pobr-core/tests/support/parse.rs +++ b/crates/pobr-core/tests/support/parse.rs @@ -35,7 +35,16 @@ static RULES: LazyLock> = LazyLock::new(|| { let path = data_root().join("overlay/mod_parser_rules.json"); let json = std::fs::read_to_string(&path).expect("读取 mod_parser_rules.json"); let doc: ModParserRulesDoc = serde_json::from_str(&json).expect("反序列化规则表"); - let mut special = load_special("overlay/special_mods.json"); + // special_mods 两层(与 pobr-gamedata `load_ruleset` 同序):版本无关策展层 + // `data/overlay-common/`(相对版本目录是 `../overlay-common/`)打底,版本层按 id + // 覆盖 / 追加,再拼 derived / vendor。 + let mut special = load_special("../overlay-common/special_mods.json"); + for v in load_special("overlay/special_mods.json") { + match special.iter_mut().find(|e| e.id == v.id) { + Some(slot) => *slot = v, + None => special.push(v), + } + } special.extend(load_special("generated/special_derived.json")); special.extend(load_special("generated/special_vendor.json")); std::sync::Arc::new( diff --git a/docs/contributing-mods.md b/docs/contributing-mods.md index a499c6fb..cc81e600 100644 --- a/docs/contributing-mods.md +++ b/docs/contributing-mods.md @@ -46,6 +46,23 @@ top of the machine-generated `.dat` import. The two files this guide is about: | `special_mods.json` | **hand** | Rust regex + value DSL | add/fix a whole-line special modifier (uniques, keystones, unusual phrasings) | | `mod_parser_rules.json` | **extracted from vendor Lua** | Lua pattern | (rarely) extend the generic grammar — but see the warning in §3 | +> **Where to add a curated `special_mods` entry — use `data/overlay-common/`.** +> `special_mods.json` is loaded in **two layers** and merged by `pobr-gamedata`: +> +> 1. `data/overlay-common/special_mods.json` — **version-independent** curated +> layer. Vendor-semantics fixes that do not change with the game patch live +> here; a new data version directory inherits them for free (no manual copy). +> **This is where new curated entries almost always go.** +> 2. `data//overlay/special_mods.json` — **version-specific** layer, +> merged on top. Only entries that genuinely differ for one game version +> belong here; a same-`id` entry here overrides the common layer, and new ids +> are appended. +> +> Practically: add your entry to `data/overlay-common/special_mods.json` unless +> it is a correction that only applies to a single game version. `regen-all.sh` +> no longer carries `special_mods.json` forward between versions — the common +> layer replaces that manual step (see `docs/version-bump-architecture.md` P1-3). + The remaining overlay files are separate domains (base-item overrides, uniques, gem effects, buff/curse definitions, stat descriptions, …). They follow their own schemas in `crates/pobr-data` and are out of scope here; the same @@ -102,7 +119,9 @@ write them by hand — the extractor fills them. ## 4. `special_mods.json` — where you add a modifier -Each entry is a `SpecialTemplateDef` (schema in `parser_rules.rs`). The parser +Add the entry to `data/overlay-common/special_mods.json` (the version-independent +layer — see §2) unless it is a correction specific to one game version. Each entry +is a `SpecialTemplateDef` (schema in `parser_rules.rs`). The parser matches the **whole line** (case-insensitive, auto-anchored `^…$`) against `pattern` (**Rust regex** — `(\d+)`, alternation, *no* look-around/back-refs), and instantiates the `mods` template using the captures. diff --git a/pipeline/regen-all.sh b/pipeline/regen-all.sh index f2775396..0b0b0fcb 100755 --- a/pipeline/regen-all.sh +++ b/pipeline/regen-all.sh @@ -16,8 +16,11 @@ # OLD_PATCH=4.5.0.3.4 pipeline/regen-all.sh # 手工域从指定旧版本沿用(跨版本升级) # # 手工策展 overlay(无 vendor/官方自动通道):buff_definitions / high_precision_mods / -# local_mods / special_mods / vendor_name_aliases —— 跨版本升级时从 OLD_PATCH 沿用, -# 并需人工复核(游戏平衡变更可能使其过时)。 +# local_mods / vendor_name_aliases —— 跨版本升级时从 OLD_PATCH 沿用,并需人工复核 +# (游戏平衡变更可能使其过时)。 +# special_mods 不在此列:已迁到版本无关的 data/overlay-common/special_mods.json, +# 新版本目录由 gamedata 加载期自动合并继承,无需逐版本沿用(P1-3)。真正版本特有的 +# special_mods 修正才留在 data//overlay/special_mods.json。 # 韧性化:不用 -e 全局中止。前置步骤(adapter base/tree)失败仍立即退出(无 base # 数据则后续无意义);overlay 单步失败只记录、续跑其余,末尾汇总。 @@ -124,7 +127,9 @@ soft_step stat_id_map "${SYNC[@]}" gen-stat-id-map --overlay-dir "$OVL" --ou # ---- 6) 手工策展 overlay:从 OLD_PATCH 沿用(需人工复核)---- echo "== [6/8] 手工策展 overlay 从 $OLD_PATCH 沿用" -for f in buff_definitions high_precision_mods local_mods special_mods vendor_name_aliases; do +# special_mods 已移出:策展条目在 data/overlay-common/,加载期合并继承(P1-3), +# 不再逐版本沿用。 +for f in buff_definitions high_precision_mods local_mods vendor_name_aliases; do src="data/$OLD_PATCH/overlay/$f.json" if [[ -f "$src" ]]; then cp "$src" "$OVL/$f.json" @@ -169,9 +174,10 @@ if [[ ${#OVERLAY_FAILURES[@]} -gt 0 ]]; then fi # ---- 6c) vendor specialModList 批量抽取 (generated/special_vendor.json) ---- -# 必须在 special_derived (步骤 4) 与 special_mods 沿用 (步骤 6) 之后:抽取器对这 -# 两个文件做 key 去重。注意去重读的是 pobr_data::data_version() 指向的数据目录, -# 不是 $PATCH——升级 drill 中先把 DATA_VERSION 常量推进到 $PATCH 再跑本脚本。 +# 必须在 special_derived (步骤 4) 之后:抽取器对 special_mods(overlay-common + +# 版本 overlay 两层)/ special_derived 做 key 去重。注意去重读的是 +# pobr_data::data_version() 指向的数据目录(含其同级 overlay-common),不是 $PATCH—— +# 升级 drill 中先把 DATA_VERSION 常量推进到 $PATCH 再跑本脚本。 # 4.5.4.3 升级曾漏掉这一步 (special_vendor 为 0 条);precompile-mods --check 现在 # 会对缺失报错。 echo "== [6c] extract-lua --what special-mods (generated/special_vendor.json)" diff --git a/tools/precompile-mods/src/check.rs b/tools/precompile-mods/src/check.rs index 5f5e0ede..b9107fd4 100644 --- a/tools/precompile-mods/src/check.rs +++ b/tools/precompile-mods/src/check.rs @@ -46,13 +46,21 @@ pub fn check(data_dir: &Path) -> Result<(), String> { None }; - // 2) special_mods.json (hand-curated) + generated/special_derived.json — - // both optional; generated/special_vendor.json — required (the 4.5.4.3 - // upgrade shipped without it because regen-all.sh lacked the step and - // nothing failed; a missing file must be loud, not a silent skip). - // All three are concatenated for the compile step below. + // 2) special_mods entries come from three sources, all concatenated for the + // compile step below (matches the runtime `GameData::load_ruleset` set): + // - overlay-common/special_mods.json (version-independent curated, P1-3) + + // overlay/special_mods.json (version-specific) — both optional; + // - generated/special_derived.json — optional; + // - generated/special_vendor.json — required (the 4.5.4.3 upgrade shipped + // without it because regen-all.sh lacked the step and nothing failed; + // a missing file must be loud, not a silent skip). + let overlay_common = data_dir + .parent() + .map(|p| p.join("overlay-common")) + .unwrap_or_else(|| data_dir.join("overlay-common")); let mut special_entries = Vec::new(); for (path, required) in [ + (overlay_common.join("special_mods.json"), false), (overlay.join("special_mods.json"), false), (generated.join("special_derived.json"), false), (generated.join("special_vendor.json"), true), diff --git a/tools/precompile-mods/tests/precompile.rs b/tools/precompile-mods/tests/precompile.rs index 76427852..b2a544bc 100644 --- a/tools/precompile-mods/tests/precompile.rs +++ b/tools/precompile-mods/tests/precompile.rs @@ -178,11 +178,24 @@ fn mirror_data_dir(src_data: &Path) -> PathBuf { std::fs::copy(&src, tmp_data.join("generated").join(name)).unwrap(); } } - // overlay/special_mods.json(parser 引擎 special 通道输入)。存在即拷。 + // overlay/special_mods.json(parser 引擎 special 通道输入,版本特有条目)。存在即拷。 let special_mods = src_data.join("overlay/special_mods.json"); if special_mods.is_file() { std::fs::copy(&special_mods, tmp_data.join("overlay/special_mods.json")).unwrap(); } + // overlay-common/special_mods.json(版本无关策展层,P1-3):gamedata 加载期把它 + // merge 到版本 overlay 之下,是引擎 special 规则的大头(133 条)。它是版本目录的 + // **同级**兄弟,隔离镜像里也必须复刻到 /data/overlay-common/,否则 fresh 重跑 + // 只见版本层零头、覆盖率跌破已提交产物。存在即拷。 + if let Some(src_common) = src_data + .parent() + .map(|p| p.join("overlay-common/special_mods.json")) + && src_common.is_file() + { + let dst_common = tmp.join("data/overlay-common"); + std::fs::create_dir_all(&dst_common).unwrap(); + std::fs::copy(&src_common, dst_common.join("special_mods.json")).unwrap(); + } // overlay/mod_parser_rules.json(引擎解析规则六表——删 legacy 后是唯一 // 解析器,缺它 precompile 直接报错)。存在即拷。 let parser_rules = src_data.join("overlay/mod_parser_rules.json"); diff --git a/tools/sync-pob-catalog/src/extract_special_mods.rs b/tools/sync-pob-catalog/src/extract_special_mods.rs index 95801b29..a3bd9a2b 100644 --- a/tools/sync-pob-catalog/src/extract_special_mods.rs +++ b/tools/sync-pob-catalog/src/extract_special_mods.rs @@ -349,21 +349,27 @@ fn repo_data_dir() -> PathBuf { /// 返回(raw key 集 = vendor_pattern ∪ pattern;regex pattern 集)。缺文件容忍 /// (version-bump 演练时可能只有部分文件)。 +/// +/// 去重源含**三处** special_mods:版本无关策展层 `data/overlay-common/special_mods.json` +/// (P1-3)、版本层 `overlay/special_mods.json`、派生表 `generated/special_derived.json`。 +/// 漏读 overlay-common 会让抽取器把已策展的 133 条当作新 vendor 条目重复产出, +/// 下游 precompile-mods --check 报重复 id。 fn load_existing_keys() -> io::Result<(BTreeSet, BTreeSet)> { let mut raw_keys = BTreeSet::new(); let mut patterns = BTreeSet::new(); - for rel in [ - "overlay/special_mods.json", - "generated/special_derived.json", - ] { - let path = repo_data_dir().join(rel); + let paths = [ + repo_data_dir().join("../overlay-common/special_mods.json"), + repo_data_dir().join("overlay/special_mods.json"), + repo_data_dir().join("generated/special_derived.json"), + ]; + for path in paths { let text = match std::fs::read_to_string(&path) { Ok(t) => t, Err(e) if e.kind() == io::ErrorKind::NotFound => continue, Err(e) => return Err(e), }; let doc: SpecialModsDef = serde_json::from_str(&text) - .map_err(|error| io::Error::other(format!("{rel} 解析失败:{error}")))?; + .map_err(|error| io::Error::other(format!("{} 解析失败:{error}", path.display())))?; for entry in doc.entries { if let Some(vp) = entry.vendor_pattern { raw_keys.insert(vp); diff --git a/tools/sync-pob-catalog/tests/extract/special_mods_patterns.rs b/tools/sync-pob-catalog/tests/extract/special_mods_patterns.rs index 42be61bd..75431529 100644 --- a/tools/sync-pob-catalog/tests/extract/special_mods_patterns.rs +++ b/tools/sync-pob-catalog/tests/extract/special_mods_patterns.rs @@ -8,20 +8,46 @@ use std::path::Path; use regex::Regex; -/// 仓库 data 根(tools/sync-pob-catalog/ → 上两级)。 -fn special_mods_path() -> std::path::PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/4.5.0.3.4/overlay/special_mods.json") +/// special_mods 两层文件(tools/sync-pob-catalog/ → 上两级到仓库根):版本无关策展层 +/// `data/overlay-common/`(P1-3,133 条大头)+ 版本层 `data//overlay/`。两者并集 +/// 覆盖全部策展 pattern。 +fn special_mods_paths() -> Vec { + let data = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data"); + vec![ + data.join("overlay-common/special_mods.json"), + data.join(pobr_data::data_version()) + .join("overlay/special_mods.json"), + ] +} + +/// 两层 entries 并集(缺文件跳过;至少一层须存在)。 +fn load_entries() -> Vec { + let mut entries = Vec::new(); + for path in special_mods_paths() { + let text = match std::fs::read_to_string(&path) { + Ok(t) => t, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue, + Err(e) => panic!("{}: {e}", path.display()), + }; + let doc: serde_json::Value = serde_json::from_str(&text).unwrap(); + entries.extend( + doc["entries"] + .as_array() + .expect("entries 数组") + .iter() + .cloned(), + ); + } + entries } /// 全部 pattern 可被 regex crate 编译(整行锚定由引擎统一加 `^...$`, /// 此处按同口径编译);捕获组数 ≥ mods 内引用的最大 `$n`。 #[test] fn all_patterns_compile_and_captures_cover_refs() { - let text = std::fs::read_to_string(special_mods_path()).expect("special_mods.json 在库"); - let doc: serde_json::Value = serde_json::from_str(&text).unwrap(); - let entries = doc["entries"].as_array().expect("entries 数组"); + let entries = load_entries(); assert!(!entries.is_empty()); - for entry in entries { + for entry in &entries { let id = entry["id"].as_str().unwrap(); let pattern = entry["pattern"].as_str().unwrap(); let re = Regex::new(&format!("^{pattern}$")) @@ -46,10 +72,8 @@ fn all_patterns_compile_and_captures_cover_refs() { /// 之外的捕获必须是显式闭集(不含 `\d`)——DSL 硬边界的「禁开放捕获」。 #[test] fn captures_are_numeric_or_closed_sets() { - let text = std::fs::read_to_string(special_mods_path()).unwrap(); - let doc: serde_json::Value = serde_json::from_str(&text).unwrap(); const NUMERIC_FORMS: [&str; 4] = [r"(\d+)", r"(\d+(?:\.\d+)?)", r"([+-]\d+)", r"(\+?\d+)"]; - for entry in doc["entries"].as_array().unwrap() { + for entry in &load_entries() { let id = entry["id"].as_str().unwrap(); let pattern = entry["pattern"].as_str().unwrap(); // 逐捕获组扫描(顶层括号,忽略 (?: 非捕获组) diff --git a/web/scripts/sync-data.mjs b/web/scripts/sync-data.mjs index f05273a6..a87e4491 100644 --- a/web/scripts/sync-data.mjs +++ b/web/scripts/sync-data.mjs @@ -39,6 +39,19 @@ for (const path of walkJson(src)) { cpSync(path, join(dest, rel)); files.push(rel); } + +// 版本无关策展层 data/overlay-common/(P1-3):版本目录的同级兄弟,gamedata 加载期 +// merge 到版本 overlay 之下。放到 dest/overlay-common/ 并以 `overlay-common/` +// 相对键登记——wasm 内存后端按该键注入,GameData 恰好从此键读取(root 父目录 + +// overlay-common)。缺目录(旧数据包)则跳过。 +const commonSrc = join(dataRoot, 'overlay-common'); +if (statSync(commonSrc, { throwIfNoEntry: false })?.isDirectory()) { + for (const path of walkJson(commonSrc)) { + const rel = join('overlay-common', relative(commonSrc, path)).split('\\').join('/'); + cpSync(path, join(dest, rel)); + files.push(rel); + } +} files.sort(); writeFileSync(join(destRoot, 'manifest.json'), JSON.stringify({ version, files }, null, 2)); From facaa451193f799d53cf5122f98abffbb5bab775 Mon Sep 17 00:00:00 2001 From: ackness Date: Thu, 16 Jul 2026 23:30:39 +0800 Subject: [PATCH 10/74] data: move version-independent curated special_mods to overlay-common 133 special_mods entries present and identical in both 4.5.0.3.4 and 4.5.4.3 are version-independent curated fixes; move them to data/overlay-common/special_mods.json. The 16 entries unique to 4.5.4.3 stay in its version overlay; 4.5.0.3.4's overlay file (all-shared, now empty) is removed and its 133 entries are inherited from overlay-common via the loader merge. Merged load per version is entry-equivalent to the pre-migration originals (4.5.4.3: 149, 4.5.0.3.4: 133; ids + content identical, no duplicates). Part of P1-3 (docs/version-bump-architecture.md). --- data/4.5.4.3/overlay/special_mods.json | 2945 +---------------- .../special_mods.json | 1644 ++++----- 2 files changed, 978 insertions(+), 3611 deletions(-) rename data/{4.5.0.3.4/overlay => overlay-common}/special_mods.json (100%) diff --git a/data/4.5.4.3/overlay/special_mods.json b/data/4.5.4.3/overlay/special_mods.json index 09d9f6fc..f4d718cc 100644 --- a/data/4.5.4.3/overlay/special_mods.json +++ b/data/4.5.4.3/overlay/special_mods.json @@ -12,2944 +12,311 @@ }, "entries": [ { - "id": "all_damage_is_taken_from_mana_before_life", - "pattern": "all damage is taken from mana before life", - "vendor_pattern": "all damage is taken from mana before life", - "mods": [ - { - "name": "DamageTakenFromManaBeforeLife", - "type": "BASE", - "value": 100 - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2389(探针求值自动转写;样本:All Damage is taken from Mana before Life)" - }, - { - "id": "allocates_passive", - "pattern": "allocates (.+)", - "vendor_pattern": "allocates (.+)", + "id": "mageblood_legacy", + "pattern": "legacy of (\\w+)", + "vendor_pattern": "legacy of (%w+)", "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5809(油涂/anoint 附魔 → GrantedPassive LIST 名;pobr 准源 legacy.rs:1067。开放捕获 (.+) 走模板直引——条件形『if you have the matching modifier on forbidden Y』未建模,本表保守不排除,corpus 无该形)", - "handler_id": "special:granted_passive", + "batch": "S3", + "source_note": "ModParser.lua:5554-5557(Mageblood 腰带变体行 → LegacyOf BASE 1 + MagebloodEquipped FLAG;捕获驱动的动态 mod 名走 handler,聚合应用见 calc/mageblood.rs)", + "handler_id": "special:mageblood_legacy", "handler_args": [ "$1" ] }, { - "id": "base_critical_hit_chance_for_spells_is", - "pattern": "base critical hit chance for spells is (\\d+)%", - "vendor_pattern": "base critical hit chance for spells is (%d+)%%", + "id": "immune_to_ignite", + "pattern": "immun[ei]t?y? to ignite", + "vendor_pattern": "immun[ei]t?y? to ignite", "mods": [ { - "name": "CritChanceBase", - "type": "OVERRIDE", - "value": "$1", - "tags": [ - { - "type": "SkillType", - "skillType": "SkillType:Spell" - } - ] + "name": "IgniteImmune", + "type": "FLAG", + "value": true } ], "verified": false, "batch": "S2", - "source_note": "ModParser.lua:5801(探针求值自动转写;样本:Base Critical Hit Chance for Spells is 15%)" + "source_note": "ModParser.lua:5172 flag(IgniteImmune);charm『Immune to Ignite』词条走此行" }, { - "id": "bow_attacks_fire_an_additional_arrow", - "pattern": "bow attacks fire an additional arrow", - "vendor_pattern": "bow attacks fire an additional arrow", + "id": "immune_to_freeze", + "pattern": "immun[ei]t?y? to freeze", + "vendor_pattern": "immun[ei]t?y? to freeze", "mods": [ { - "name": "ProjectileCount", - "type": "BASE", - "value": 1, - "keyword_flags": [ - "Arrow" - ] + "name": "FreezeImmune", + "type": "FLAG", + "value": true } ], "verified": false, "batch": "S2", - "source_note": "ModParser.lua:4795(探针求值自动转写;样本:Bow Attacks fire an additional Arrow)" + "source_note": "ModParser.lua:5168 flag(FreezeImmune);Thawing Charm『Immune to Freeze』词条走此行" }, { - "id": "break_increased_armour", - "pattern": "break (\\d+)% increased armour", - "vendor_pattern": "break (%d+)%% increased armour", + "id": "recover_mana_when_used", + "pattern": "recovers? (\\d+) mana when used", + "vendor_pattern": null, "mods": [ { - "name": "ArmourBreakPerHit", - "type": "INC", + "name": "ManaOnFlaskUse", + "type": "BASE", "value": "$1" } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4258(探针求值自动转写;样本:Break 10% increased Armour)" - }, - { - "id": "buffs_on_you_expire_slower", - "pattern": "buffs on you expire (\\d+)% slower", - "vendor_pattern": "buffs on you expire (%d+)%% slower", - "mods": [ - { - "name": "Duration", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "SkillType", - "skillType": "SkillType:Buff" - } - ] - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4532(探针求值自动转写;样本:Buffs on you expire 10% slower)" + "batch": "S0", + "source_note": "药剂/护符使用时瞬时回魔:解析为 ManaOnFlaskUse BASE(归因/Calcs 可见;稳态面板无消费——瞬时恢复不属于稳态模型,flask 恢复面板落地时接线)。vendor 走 flaskData 本地系统不产 mod" }, { - "id": "cannot_be_blinded_while_on_full_life", - "pattern": "cannot be blinded while on full life", - "vendor_pattern": "cannot be blinded while on full life", + "id": "flask_mana_recovery_overflow", + "pattern": "mana recovery from flasks can overflow maximum mana( during effect)?", + "vendor_pattern": null, "mods": [ { - "name": "Condition:CannotBeBlinded", + "name": "FlaskManaRecoveryOverflow", "type": "FLAG", - "value": true, - "tags": [ - { - "type": "Condition", - "var": "FullLife" - } - ] + "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5123(探针求值自动转写;样本:Cannot be Blinded while on Full Life)" + "batch": "S0", + "source_note": "Melting Maelstrom:效果期间药剂魔力恢复可溢出上限 → FLAG 进 ModDb(归因可见;溢出魔力稳态无消费者,vendor 亦未建模)" }, { - "id": "cannot_be_heavy_stunned_while_sprinting", - "pattern": "cannot be heavy stunned while sprinting", - "vendor_pattern": "cannot be heavy stunned while sprinting", + "id": "possessed_by_spirit_on_use", + "pattern": "possessed by [a-z' ]+ for (\\d+) seconds on use", + "vendor_pattern": null, "mods": [ { - "name": "StunImmune", + "name": "SpiritPossessionOnUse", "type": "FLAG", - "value": true, - "tags": [ - { - "type": "Condition", - "var": "Sprinting" - } - ] + "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3205(探针求值自动转写;样本:Cannot be Heavy Stunned while Sprinting)" + "batch": "S0", + "source_note": "金护符阿兹莫里之灵附身(Stag/Wolf/Bear…):玩家侧 buff 数值无可验证一手来源(poe2db/wiki 仅载敌方附身效果,BuffDefinitions 未入库)→ 暂解析为 FLAG 登记,数据可得后升级 handler 接具体 buff" }, { - "id": "cannot_be_ignited", - "pattern": "cannot be ignited", - "vendor_pattern": "cannot be ignited", + "id": "also_grants_guard", + "pattern": "also grants (\\d+) guard", + "vendor_pattern": null, "mods": [ { - "name": "IgniteImmune", - "type": "FLAG", - "value": true + "name": "GuardAbsorbRate", + "type": "BASE", + "value": 100 + }, + { + "name": "GuardAbsorbLimit", + "type": "BASE", + "value": "$1" } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5131(探针求值自动转写;样本:Cannot be Ignited)" + "batch": "S0", + "source_note": "药剂/护符使用效果:PoE2 Guard buff=生命/ES 前全额吸收击中的缓冲池(agent-docs/active-defences.md §2;poe2db Guard 定义)→ rate 100 + limit $1,消费端 pool_setup.rs:132 现成(EHP/MaxHit 承受层)。vendor ModParser 未接此词条(比 PoB2 多建模一层)" }, { - "id": "cannot_immobilise_enemies", - "pattern": "cannot immobilise enemies", - "vendor_pattern": "cannot immobilise enemies", + "id": "armour_applies_to_fcl_instead_of_physical", + "pattern": "armour applies to fire, cold and lightning damage taken from hits instead of physical damage", + "vendor_pattern": "armour applies to fire, cold and lightning damage taken from hits instead of physical damage", "mods": [ { - "name": "CannotElectrocute", - "type": "FLAG", - "value": true + "name": "ArmourAppliesToFireDamageTaken", + "type": "BASE", + "value": 100 }, { - "name": "CannotFreeze", - "type": "FLAG", - "value": true + "name": "ArmourAppliesToColdDamageTaken", + "type": "BASE", + "value": 100 }, { - "name": "CannotHeavyStun", - "type": "FLAG", - "value": true + "name": "ArmourAppliesToLightningDamageTaken", + "type": "BASE", + "value": 100 }, { - "name": "CannotPin", + "name": "ArmourDoesNotApplyToPhysicalDamageTaken", "type": "FLAG", "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5735(探针求值自动转写;样本:Cannot Immobilise enemies)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:2545-2550(三元素 ArmourAppliesToDamageTaken BASE 100 + instead 变体专属 flag)。消费端 taken.rs armour_applies_pct(flag 时物理份额归 0)。legacy-only 测试掩盖的 engine 缺口(原 mod_parser_m2_defence.rs:182)" }, { - "id": "cannot_regenerate_mana_if_you_haven_t_dealt_a_critical_hit_recently", - "pattern": "cannot regenerate mana if you haven't dealt a critical hit recently", - "vendor_pattern": "cannot regenerate mana if you haven't dealt a critical hit recently", + "id": "pct_of_armour_applies_to_fcl", + "pattern": "(\\d+)% of armour applies to fire, cold and lightning damage taken from hits", + "vendor_pattern": "(%d+)%% of armour applies to fire, cold and lightning damage taken from hits", "mods": [ { - "name": "NoManaRegen", - "type": "FLAG", - "value": true, - "tags": [ - { - "type": "Condition", - "neg": true, - "var": "CritRecently" - } - ] - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5028(探针求值自动转写;样本:Cannot Regenerate Mana if you haven't dealt a Critical Hit Recently)" - }, - { - "id": "chance_for_damage_with_hits_to_be_lucky", - "pattern": "(\\d+)% chance for () ?damage with hits to be lucky", - "vendor_pattern": "(%d+)%% chance for (%w*) ?damage with hits to be lucky", - "mods": [ + "name": "ArmourAppliesToFireDamageTaken", + "type": "BASE", + "value": "$1" + }, { - "name": "LuckyHitsChance", + "name": "ArmourAppliesToColdDamageTaken", "type": "BASE", "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4255(探针求值自动转写;样本:20% chance for Damage with Hits to be Lucky)" - }, - { - "id": "charm_slots_colon", - "pattern": "charm slots: (\\d+)", - "vendor_pattern": "h?a?s? ?+?(%d+) charm slots?", - "mods": [ + }, { - "name": "CharmLimit", + "name": "ArmourAppliesToLightningDamageTaken", "type": "BASE", "value": "$1" } ], "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:5453(CharmLimit;冒号格式 'Charm Slots: N',与 has_charm_slots 互补,命中 18 build 首位缺口)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:2551-2555(function(num)→三元素 BASE num)。消费端 taken.rs armour_applies_pct。原 mod_parser_m2_defence.rs:192" }, { - "id": "ci_maximum_life_is_1", - "pattern": "maximum life is 1", + "id": "armour_applies_to_elemental_damage", + "pattern": "armour applies to elemental damage", + "vendor_pattern": "armour applies to elemental damage", "mods": [ { - "name": "MaximumLife", - "type": "OVERRIDE", - "value": 1.0 + "name": "ArmourAppliesToFireDamageTaken", + "type": "BASE", + "value": 100 + }, + { + "name": "ArmourAppliesToColdDamageTaken", + "type": "BASE", + "value": 100 }, { - "name": "ChaosInoculation", - "type": "FLAG", - "value": true + "name": "ArmourAppliesToLightningDamageTaken", + "type": "BASE", + "value": 100 } ], "verified": false, - "batch": "S0", - "source_note": "Chaos Inoculation(PoE2 树文本);pobr 准源 mod_parser.rs:524-528;vendor 近似 key = 'maximum life becomes 1, immune to chaos damage'(:2354,另含 ChaosDamageTaken MORE -100——pobr 暂未消费,B-3 升级时对齐)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:2556-2560(三元素 BASE 100,无 instead flag)。原 mod_parser_m2_defence.rs:201" }, { - "id": "convert_pct_max_es_to_mana", - "pattern": "convert (\\d+(?:\\.\\d+)?)% of maximum energy shield to maximum mana", - "vendor_pattern": "convert (%d+)%% of maximum energy shield to maximum mana", + "id": "pct_of_armour_applies_to_elemental_damage", + "pattern": "\\+?(\\d+)% of armour (?:also )?applies to elemental damage", + "vendor_pattern": "%+?(%d+)%% of armour (a?l?s?o? ?)applies to elemental damage", "mods": [ { - "name": "EnergyShieldConvertToMana", + "name": "ArmourAppliesToFireDamageTaken", "type": "BASE", "value": "$1" - } - ], - "verified": false, - "batch": "S0", - "source_note": "pobr 准源 mod_parser.rs:508-522;vendor ModParser.lua:2396 同形" - }, - { - "id": "converts_all_energy_shield_to_mana", - "pattern": "converts all energy shield to mana", - "vendor_pattern": "converts all energy shield to mana", - "mods": [ + }, { - "name": "EnergyShieldConvertToMana", + "name": "ArmourAppliesToColdDamageTaken", "type": "BASE", - "value": 100.0 - } - ], - "verified": false, - "batch": "S0", - "source_note": "Eldritch Battery 型;pobr 准源 mod_parser.rs:533-538;vendor ModParser.lua:2395" - }, - { - "id": "critical_hits_ignore_non_negative_enemy_monster_elemental_resistances", - "pattern": "critical hits ignore non-negative enemy monster elemental resistances", - "vendor_pattern": "critical hits ignore non%-negative enemy monster elemental resistances", - "mods": [ + "value": "$1" + }, { - "name": "IgnoreNonNegativeEleRes", - "type": "FLAG", - "value": true, - "tags": [ - { - "type": "Condition", - "var": "CriticalStrike" - } - ] + "name": "ArmourAppliesToLightningDamageTaken", + "type": "BASE", + "value": "$1" } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3372(探针求值自动转写;样本:Critical Hits ignore non-negative Enemy Monster Elemental Resistances)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:2561-2565(function(num)→三元素 BASE num;vendor 第二捕获 also 为可选填充词,此处转非捕获组保持 $1 语义)" }, { - "id": "cursed_enemies_killed_by_you_or_by_allies_in_your_presence_have_a_chan", - "pattern": "cursed enemies killed by you, or by allies in your presence, have a (\\d+)% chance to explode, dealing a (quarter) of their maximum life as (chaos) damage", - "vendor_pattern": "cursed enemies killed by you, or by allies in your presence, have a (%d+)%% chance to explode, dealing a (.+) of their maximum life as (.+) damage", + "id": "energy_shield_protects_mana_instead_of_life", + "pattern": "energy shield protects mana instead of life", + "vendor_pattern": "energy shield protects mana instead of life", "mods": [ { - "name": "ExplodeMod", - "type": "LIST", - "value": { - "amount": 25, - "keyOfScaledMod": "value", - "type": "Chaos", - "value": "$1" - }, - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Cursed" - } - ] - }, - { - "name": "CanExplode", + "name": "EnergyShieldProtectsMana", "type": "FLAG", "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2260(探针求值自动转写;样本:Cursed Enemies killed by you, or by Allies in your Presence, have a 33% chance to explode, dealing a quarter of their maximum Life as Chaos damage)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:2465(Eldritch Battery 类)。消费端 keystone_registry.rs DefenceKeystones.energy_shield_protects_mana → pool_damage.rs/ehp.rs/defence.rs。原 mod_parser_m2_defence.rs:99" }, { - "id": "curses_you_inflict_ignore_curse_limit", - "pattern": "curses you inflict ignore curse limit", - "vendor_pattern": "curses you inflict ignore curse limit", + "id": "converts_all_evasion_rating_to_armour", + "pattern": "converts all evasion rating to armour", + "vendor_pattern": "converts all evasion rating to armour", "mods": [ { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": 99 - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4275(探针求值自动转写;样本:Curses you inflict ignore Curse limit)" - }, - { - "id": "defend_with_pct_of_armour", - "pattern": "defend with (\\d+(?:\\.\\d+)?)% of armour(?: during effect)?", - "vendor_pattern": "defend with (%d+)%% of armour", - "mods": [ + "name": "IronReflexes", + "type": "FLAG", + "value": true + }, { - "name": "ArmourDefense", + "name": "EvasionConvertToArmour", "type": "BASE", - "value": { - "ref": "$1", - "ops": [ - { - "base": -100.0 - } - ] - } + "value": 100 } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2616/:2619(Defend with N% of Armour [during effect] → ArmourDefense BASE N-100;pobr 准源 legacy.rs:1715。during effect 变体同无条件,消费侧 max_of)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:2369(Iron Reflexes 类:flag + EvasionConvertToArmour BASE 100)。消费端 defence.rs calc_defence_resources 五元 ConvertTo 矩阵 + keystone_registry.rs iron_reflexes(Unbreakable 联动)。原 mod_parser_m2_defence.rs:265" }, { - "id": "double_activation_delay_of_curses", - "pattern": "double activation delay of curses", - "vendor_pattern": "double activation delay of curses", + "id": "chance_to_deflect_is_lucky", + "pattern": "chance to deflect is lucky", + "vendor_pattern": "chance to deflect is lucky", "mods": [ { - "name": "CurseDelay", - "type": "MORE", - "value": 100 + "name": "DeflectIsLucky", + "type": "FLAG", + "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4278(探针求值自动转写;样本:Double Activation Delay of Curses)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:4202。消费端 defence_panels.rs:298 偏斜几率 lucky 幂(1−(1−p)²)。原 mod_parser_m2_defence.rs:397" }, { - "id": "empowered_attacks_deal_increased_damage", - "pattern": "empowered attacks deal (\\d+)% increased damage", - "vendor_pattern": "empowered attacks deal (%d+)%% increased damage", + "id": "chance_to_block_damage_is_lucky", + "pattern": "chance to block damage is lucky", + "vendor_pattern": "chance to block damage is lucky", "mods": [ { - "name": "Damage", - "type": "INC", - "value": "$1", - "flags": [ - "Attack" - ], - "tags": [ - { - "type": "Condition", - "var": "Empowered" - } - ] + "name": "BlockChanceIsLucky", + "type": "FLAG", + "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2647(Empowered Attacks deal N% increased Damage;vendor 产 EmpoweredIncrease INC,pobr legacy.rs:367 产 Damage INC flags=Attack + Condition:Empowered——双跑收敛以 legacy 为基准。Empowered 默认不激活)" + "batch": "wave2-defence", + "source_note": "ModParser.lua:4371。消费端 defence_panels.rs:174 effective(BlockChance) luck_transform" }, { - "id": "enemies_you_kill_with_empowered_attacks_have_a_chance_to_explode_deali", - "pattern": "enemies you kill with empowered attacks have a (\\d+)% chance to explode, dealing a (tenth) of their maximum life as (fire) damage", - "vendor_pattern": "enemies you kill with empowered attacks have a (%d+)%% chance to explode, dealing a (.+) of their maximum life as (.+) damage", + "id": "chance_to_block_is_lucky", + "pattern": "(?:your )?chance to block is lucky", + "vendor_pattern": "y?o?u?r? ?chance to block is lucky", "mods": [ { - "name": "ExplodeMod", - "type": "LIST", - "value": { - "amount": 10, - "keyOfScaledMod": "value", - "type": "Fire", - "value": "$1" - } + "name": "BlockChanceIsLucky", + "type": "FLAG", + "value": true }, { - "name": "CanExplode", + "name": "ProjectileBlockChanceIsLucky", "type": "FLAG", "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2242(探针求值自动转写;样本:Enemies you kill with Empowered Attacks have a 10% chance to Explode, dealing a tenth of their maximum Life as Fire Damage)" - }, - { - "id": "enemy_critical_hit_chance_against_you_is_unlucky", - "pattern": "enemy critical hit chance against you is unlucky", - "vendor_pattern": "enemy critical hit chance against you is unlucky", - "mods": [ + }, { - "name": "EnemyUnluckyCrit", + "name": "SpellBlockChanceIsLucky", "type": "FLAG", "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3740(探针求值自动转写;样本:Enemy Critical Hit Chance against you is Unlucky)" - }, - { - "id": "energy_shield_does_not_recharge", - "pattern": "energy shield does not recharge", - "vendor_pattern": "energy shield does not recharge", - "mods": [ + }, { - "name": "NoEnergyShieldRecharge", + "name": "SpellProjectileBlockChanceIsLucky", "type": "FLAG", "value": true } ], "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5030(探针求值自动转写;样本:Energy Shield does not Recharge)" - }, - { - "id": "equipment_and_skill_gems_have_reduced_attribute_requirements", - "pattern": "equipment and skill gems have (\\d+)% reduced attribute requirements", - "vendor_pattern": "equipment and skill gems have (%d+)%% reduced attribute requirements", - "mods": [ - { - "name": "GlobalAttributeRequirements", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5610(探针求值自动转写;样本:Equipment and Skill Gems have 4% reduced Attribute Requirements)" - }, - { - "id": "exposure_you_inflict_lowers_resistances_by_an_additional", - "pattern": "exposure you inflict lowers resistances by an additional (5)%", - "vendor_pattern": "exposure you inflict lowers resistances by an additional (%-?%d+)%%", - "mods": [ - { - "name": "ExtraExposure", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2917(探针求值自动转写;样本:Exposure you inflict lowers Resistances by an additional 5%)" - }, - { - "id": "flasks_gain_charges_per_second", - "pattern": "flasks gain (0\\.17) charges? per second", - "vendor_pattern": "flasks gain (%d*%.?%d+) charges? per second", - "mods": [ - { - "name": "FlaskChargesGenerated", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5441(探针求值自动转写;样本:Flasks gain 0.17 charges per Second)" - }, - { - "id": "gain_deflection_rating_equal_to_of_armour", - "pattern": "gain deflection rating equal to (\\d+)% of armour", - "vendor_pattern": "gain deflection rating equal to (%d+)%% of armour", - "mods": [ - { - "name": "ArmourGainAsDeflection", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4159(探针求值自动转写;样本:Gain Deflection Rating equal to 20% of Armour)" - }, - { - "id": "gain_deflection_rating_equal_to_of_evasion_rating", - "pattern": "gain deflection rating equal to (\\d+)% of evasion rating", - "vendor_pattern": "gain deflection rating equal to (%d+)%% of evasion rating", - "mods": [ - { - "name": "EvasionGainAsDeflection", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:4158(探针求值自动转写;样本:Gain Deflection Rating equal to 21% of Evasion Rating)" - }, - { - "id": "gain_mana_per_enemy_killed", - "pattern": "gain (\\d+) mana per enemy killed", - "vendor_pattern": "(%d+) mana per enemy killed", - "mods": [ - { - "name": "ManaOnKill", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:543(mana per enemy killed→ManaOnKill BASE)" - }, - { - "id": "gain_pct_damage_as_extra_all_elements", - "pattern": "gain (\\d+(?:\\.\\d+)?)% of damage as extra damage of all elements", - "vendor_pattern": "gain (%d+)%% of damage as extra damage of all elements", - "mods": [ - { - "name": "DamageGainAsFire", - "type": "BASE", - "value": "$1" - }, - { - "name": "DamageGainAsCold", - "type": "BASE", - "value": "$1" - }, - { - "name": "DamageGainAsLightning", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2244 等(Gain N% of Damage as Extra Damage of all Elements → DamageGainAs{Fire,Cold,Lightning} BASE N,三系展开;pobr 准源 legacy gain-as 通道)" - }, - { - "id": "gain_rage_on_melee_hit", - "pattern": "gain \\d+ rage on melee hit", - "vendor_pattern": "gain %d+ rage on melee hit", - "mods": [ - { - "name": "Condition:CanGainRage", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:2761(探针求值自动转写;样本:Gain 1 Rage on Melee Hit)" - }, - { - "id": "grants_passive_skill_point", - "pattern": "grants (\\d+) passive skill points?", - "vendor_pattern": "grants (%d+) passive skill points?", - "mods": [ - { - "name": "ExtraPoints", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:2727(探针求值自动转写;样本:Grants 1 Passive Skill Point)" - }, - { - "id": "grenade_skills_fire_an_additional_projectile", - "pattern": "grenade skills fire an additional projectile", - "vendor_pattern": "grenade skills fire an additional projectile", - "mods": [ - { - "name": "ProjectileCount", - "type": "BASE", - "value": 1, - "flags": [ - "Projectile" - ], - "tags": [ - { - "type": "SkillType", - "skillType": "SkillType:Grenade" - } - ] - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:4803(探针求值自动转写;样本:Grenade Skills Fire an additional Projectile)" - }, - { - "id": "grenades_have_chance_to_activate_a_second_time", - "pattern": "grenades have (\\d+)% chance to activate a second time", - "vendor_pattern": "grenades have (%d+)%% chance to activate a second time", - "mods": [ - { - "name": "GrenadeActivateTwice", - "type": "BASE", - "value": "$1", - "tags": [ - { - "type": "SkillType", - "skillType": "SkillType:Grenade" - } - ] - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4804(探针求值自动转写;样本:Grenades have 15% chance to activate a second time)" - }, - { - "id": "has_charm_slots", - "pattern": "h?a?s? ?\\+?(\\d+) charm slots?", - "vendor_pattern": "h?a?s? ?+?(%d+) charm slots?", - "mods": [ - { - "name": "CharmLimit", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:5453(探针求值自动转写;样本:Has 3 Charm Slots)" - }, - { - "id": "has_no_attribute_requirements", - "pattern": "has no attribute requirements", - "vendor_pattern": "has no attribute requirements", - "mods": [ - { - "name": "NoAttributeRequirements", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3464(探针求值自动转写;样本:Has no Attribute Requirements)" - }, - { - "id": "has_to_defence_per_player_level", - "pattern": "has \\+(\\d+) to (armour|evasion|evasion rating|energy shield|maximum energy shield) per player level", - "vendor_pattern": "has %+(%d+) to (.+) per player level", - "mods": [ - { - "name": { - "enum": 2 - }, - "type": "BASE", - "value": "$1" - } - ], - "enums": { - "2": { - "armour": "ArmourPerLevel", - "evasion": "EvasionPerLevel", - "evasion rating": "EvasionPerLevel", - "energy shield": "EnergyShieldPerLevel", - "maximum energy shield": "EnergyShieldPerLevel" - } - }, - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3400-3402(Has +N to per player level → PerLevel BASE,件级局部底值;pobr 准源 legacy.rs:640)" - }, - { - "id": "has_to_maximum_runic_ward_per_player_level", - "pattern": "has ([+-]\\d+) to maximum runic ward per player level", - "vendor_pattern": "has ([%+%-]%d+) to maximum runic ward per player level", - "mods": [ - { - "name": "WardPerLevel", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3402(探针求值自动转写;样本:Has +1 to maximum Runic Ward per player level)" - }, - { - "id": "hits_have_reduced_critical_hit_chance_against_you", - "pattern": "hits have (\\d+)% reduced critical hit chance against you", - "vendor_pattern": "hits have (%d+)%% reduced critical hit chance against you", - "mods": [ - { - "name": "EnemyCritChance", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3771(探针求值自动转写;样本:Hits have 25% reduced Critical Hit Chance against you)" - }, - { - "id": "hits_ignore_non_negative_elemental_resistances_of_frozen_enemies", - "pattern": "hits ignore non-negative elemental resistances of frozen enemies", - "vendor_pattern": "hits ignore non%-negative elemental resistances of frozen enemies", - "mods": [ - { - "name": "IgnoreNonNegativeEleRes", - "type": "FLAG", - "value": true, - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Frozen" - } - ] - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5994(探针求值自动转写;样本:Hits ignore non-negative Elemental Resistances of Frozen Enemies)" - }, - { - "id": "ignore_all_movement_penalties_from_armour", - "pattern": "ignore all movement penalties from armour", - "vendor_pattern": "ignore all movement penalties from armour", - "mods": [ - { - "name": "Condition:IgnoreMovementPenalties", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5112(探针求值自动转写;样本:Ignore all Movement Penalties from Armour)" - }, - { - "id": "immune_to_chaos_damage", - "pattern": "immune to chaos damage", - "vendor_pattern": "immune to chaos damage", - "mods": [], - "verified": false, - "batch": "S0", - "source_note": "纯识别条目(pobr 现状 = Unsupported 不产 mod,mod_parser.rs:539-548);vendor :2358 产 ChaosInoculation flag + ChaosDamageTaken MORE -100——B-3 行为升级时改 mods 并对拍" - }, - { - "id": "immune_to_chaos_damage_and_bleeding", - "pattern": "immune to chaos damage and bleeding", - "vendor_pattern": "immune to chaos damage and bleeding", - "mods": [], - "verified": false, - "batch": "S0", - "source_note": "纯识别条目(pobr 现状 = Unsupported,mod_parser.rs:539-548);vendor :2362 另含 BleedImmune flag" - }, - { - "id": "immune_to_poison", - "pattern": "immun[ei]t?y? to poison", - "vendor_pattern": "immun[ei]t?y? to poison", - "mods": [ - { - "name": "PoisonImmune", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5162(探针求值自动转写;样本:Immune to Poison)" - }, - { - "id": "increased_bonuses_gained_from_equipped_quiver", - "pattern": "(\\d+)% increased bonuses gained from equipped quiver", - "vendor_pattern": "(%d+)%% increased bonuses gained from equipped quiver", - "mods": [ - { - "name": "EffectOfBonusesFromQuiver", - "type": "INC", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4866(探针求值自动转写;样本:6% increased bonuses gained from Equipped Quiver)" - }, - { - "id": "increased_bonuses_gained_from_equipped_rings_and_amulets", - "pattern": "(\\d+)% increased bonuses gained from equipped rings and amulets", - "vendor_pattern": "(%d+)%% increased bonuses gained from equipped rings and amulets", - "mods": [ - { - "name": "EffectOfBonusesFromRing 1", - "type": "INC", - "value": "$1" - }, - { - "name": "EffectOfBonusesFromRing 2", - "type": "INC", - "value": "$1" - }, - { - "name": "EffectOfBonusesFromRing 3", - "type": "INC", - "value": "$1" - }, - { - "name": "EffectOfBonusesFromAmulet", - "type": "INC", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4873(探针求值自动转写;样本:25% increased bonuses gained from Equipped Rings and Amulets)" - }, - { - "id": "increased_damage_for_each_type_of_elemental_ailment_on_enemy", - "pattern": "(\\d+)% increased damage for each type of elemental ailment on enemy", - "vendor_pattern": "(%d+)%% increased damage for each type of elemental ailment on enemy", - "mods": [ - { - "name": "Damage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Electrocuted" - } - ] - }, - { - "name": "Damage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Frozen" - } - ] - }, - { - "name": "Damage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Chilled" - } - ] - }, - { - "name": "Damage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Ignited" - } - ] - }, - { - "name": "Damage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "ActorCondition", - "actor": "enemy", - "var": "Shocked" - } - ] - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:3798(探针求值自动转写;样本:21% increased Damage for each type of Elemental Ailment on Enemy)" - }, - { - "id": "increased_effect_of_small_passive_skills", - "pattern": "(\\d+)% increased effect of small passive skills", - "vendor_pattern": "(%d+)%% increased effect of small passive skills", - "mods": [ - { - "name": "SmallPassiveSkillEffect", - "type": "INC", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3281(探针求值自动转写;样本:50% increased effect of Small Passive Skills)" - }, - { - "id": "increased_energy_shield_recharge_rate", - "pattern": "(\\d+)% increased energy shield recharge rate", - "vendor_pattern": "(%d+)%% increased energy shield recharge rate", - "mods": [ - { - "name": "EnergyShieldRecharge", - "type": "INC", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:234(energy shield recharge rate→EnergyShieldRecharge;form parser modNameList 未含,M6 名表缺口)" - }, - { - "id": "increased_skill_effect_duration", - "pattern": "(\\d+)% increased skill effect duration", - "vendor_pattern": "(%d+)%% increased skill effect duration", - "mods": [ - { - "name": "Duration", - "type": "INC", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:651(skill effect duration→Duration;form parser modNameList 未含,M6 名表缺口)" - }, - { - "id": "increased_total_power_counted_by_warcries", - "pattern": "(\\d+)% increased total power counted by warcries", - "vendor_pattern": "(%d+)%% increased total power counted by warcries", - "mods": [ - { - "name": "WarcryPower", - "type": "INC", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5774(探针求值自动转写;样本:5% increased total Power counted by Warcries)" - }, - { - "id": "increases_and_reductions_to_projectile_speed_also_apply_to_damage_with", - "pattern": "increases and reductions to projectile speed also apply to damage with bows", - "vendor_pattern": "increases and reductions to projectile speed also apply to damage with bows", - "mods": [ - { - "name": "ProjectileSpeedAppliesToBowDamage", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3648(探针求值自动转写;样本:Increases and Reductions to Projectile Speed also apply to Damage with Bows)" - }, - { - "id": "inevitable_critical_hits", - "pattern": "inevitable critical hits", - "vendor_pattern": "inevitable critical hits", - "mods": [ - { - "name": "InevitableCriticalHits", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3393(探针求值自动转写;样本:Inevitable Critical Hits)" - }, - { - "id": "inherent_life_granted_by_strength_is_halved", - "pattern": "inherent life granted by strength is halved", - "vendor_pattern": "inherent life granted by strength is halved", - "mods": [ - { - "name": "HalvesLifeFromStrength", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2329(探针求值自动转写;样本:Inherent Life granted by Strength is halved)" - }, - { - "id": "leech_of_physical_attack_damage_as_life", - "pattern": "leeche?s? (\\d+(?:\\.\\d+)?)% of (physical) attack damage as life", - "vendor_pattern": "leeche?s? ([%d%.]+)%% of (%a+) attack damage as life", - "mods": [ - { - "name": "PhysicalDamageLifeLeech", - "type": "BASE", - "value": "$1", - "flags": [ - "Attack" - ] - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:2671(探针求值自动转写;样本:Leech 7.88% of Physical Attack Damage as Life)" - }, - { - "id": "leech_of_physical_attack_damage_as_mana", - "pattern": "leeche?s? (\\d+(?:\\.\\d+)?)% of (physical) attack damage as mana", - "vendor_pattern": "leeche?s? ([%d%.]+)%% of (%a+) attack damage as mana", - "mods": [ - { - "name": "PhysicalDamageManaLeech", - "type": "BASE", - "value": "$1", - "flags": [ - "Attack" - ] - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:2668(探针求值自动转写;样本:Leech 8.74% of Physical Attack Damage as Mana)" - }, - { - "id": "less_movement_speed_penalty_from_using_skills_while_moving", - "pattern": "(\\d+)% less movement speed penalty from using skills while moving", - "vendor_pattern": "(%d+)%% less movement speed penalty from using skills while moving", - "mods": [ - { - "name": "MovementSpeedPenalty", - "type": "MORE", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:6018(探针求值自动转写;样本:30% less Movement Speed Penalty from using Skills while moving)" - }, - { - "id": "life_leech_recovers_based_on_your_lightning_damage_as_well_as_physical", - "pattern": "(life) leech recovers based on y?o?u?r? ?(lightning) damage as well as physical damage", - "vendor_pattern": "(%a+%s?%a*) leech recovers based on y?o?u?r? ?(%a+) damage as well as physical damage", - "mods": [ - { - "name": "LifeLeechBasedOnLightningDamage", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:2677(探针求值自动转写;样本:Life Leech recovers based on your Lightning damage as well as Physical damage)" - }, - { - "id": "life_regeneration_per_second", - "pattern": "(\\d+(?:\\.\\d+)?) life regeneration per second", - "vendor_pattern": "(%d+%.?%d*) life regeneration per second", - "mods": [ - { - "name": "LifeRegen", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:175(life regeneration→LifeRegen BASE 平直每秒回复)" - }, - { - "id": "mageblood_legacy", - "pattern": "legacy of (\\w+)", - "vendor_pattern": "legacy of (%w+)", - "verified": false, - "batch": "S3", - "source_note": "ModParser.lua:5554-5557(Mageblood 腰带变体行 → LegacyOf BASE 1 + MagebloodEquipped FLAG;捕获驱动的动态 mod 名走 handler,聚合应用见 calc/mageblood.rs)", - "handler_id": "special:mageblood_legacy", - "handler_args": [ - "$1" - ] - }, - { - "id": "magnitudes_of_curses_you_inflict_are_zero", - "pattern": "magnitudes of curses you inflict are zero", - "vendor_pattern": "magnitudes of curses you inflict are zero", - "mods": [ - { - "name": "CurseEffect", - "type": "MORE", - "value": -100 - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:4276(探针求值自动转写;样本:Magnitudes of Curses you inflict are zero)" - }, - { - "id": "mana_costs_are_doubled", - "pattern": "d?o?u?b?l?e?s? ?mana costs ?a?r?e? ?d?o?u?b?l?e?d?", - "vendor_pattern": "d?o?u?b?l?e?s? ?mana costs ?a?r?e? ?d?o?u?b?l?e?d?", - "mods": [ - { - "name": "ManaCost", - "type": "MORE", - "value": 100 - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:2393(探针求值自动转写;样本:Mana Costs are Doubled)" - }, - { - "id": "no_inherent_mana_regeneration", - "pattern": "no inherent mana regeneration", - "vendor_pattern": "no inherent mana regeneration", - "mods": [ - { - "name": "Condition:NoInherentManaRegen", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3127(探针求值自动转写;样本:No inherent Mana Regeneration)" - }, - { - "id": "no_rage_effect", - "pattern": "no rage effect", - "vendor_pattern": "no rage effect", - "mods": [ - { - "name": "RageEffect", - "type": "OVERRIDE", - "value": 0 - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2788(探针求值自动转写;样本:No Rage effect)" - }, - { - "id": "of_armour_also_applies_to_cold_damage", - "pattern": "\\+?(\\d+)% of armour also applies to (cold) damage", - "vendor_pattern": "%+?(%d+)%% of armour also applies to (%a+) damage", - "mods": [ - { - "name": "ArmourAppliesToColdDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2543(探针求值自动转写;样本:+15% of Armour also applies to Cold Damage)" - }, - { - "id": "of_armour_also_applies_to_elemental_damage", - "pattern": "\\+?(\\d+)% of armour (also )applies to elemental damage", - "vendor_pattern": "%+?(%d+)%% of armour (a?l?s?o? ?)applies to elemental damage", - "mods": [ - { - "name": "ArmourAppliesToFireDamageTaken", - "type": "BASE", - "value": "$1" - }, - { - "name": "ArmourAppliesToColdDamageTaken", - "type": "BASE", - "value": "$1" - }, - { - "name": "ArmourAppliesToLightningDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2535(探针求值自动转写;样本:+5% of Armour also applies to Elemental Damage)" - }, - { - "id": "of_armour_also_applies_to_fire_damage", - "pattern": "\\+?(\\d+)% of armour also applies to (fire) damage", - "vendor_pattern": "%+?(%d+)%% of armour also applies to (%a+) damage", - "mods": [ - { - "name": "ArmourAppliesToFireDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2543(探针求值自动转写;样本:+15% of Armour also applies to Fire Damage)" - }, - { - "id": "of_armour_also_applies_to_lightning_damage", - "pattern": "\\+?(\\d+)% of armour also applies to (lightning) damage", - "vendor_pattern": "%+?(%d+)%% of armour also applies to (%a+) damage", - "mods": [ - { - "name": "ArmourAppliesToLightningDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2543(探针求值自动转写;样本:+15% of Armour also applies to Lightning Damage)" - }, - { - "id": "of_damage_taken_bypasses_energy_shield", - "pattern": "(\\d+)% of damage taken bypasses energy shield", - "vendor_pattern": "(%d+)%% of damage taken bypasses energy shield", - "mods": [ - { - "name": "PhysicalEnergyShieldBypass", - "type": "BASE", - "value": "$1" - }, - { - "name": "LightningEnergyShieldBypass", - "type": "BASE", - "value": "$1" - }, - { - "name": "ColdEnergyShieldBypass", - "type": "BASE", - "value": "$1" - }, - { - "name": "FireEnergyShieldBypass", - "type": "BASE", - "value": "$1" - }, - { - "name": "ChaosEnergyShieldBypass", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:2495(探针求值自动转写;样本:12% of Damage taken bypasses Energy Shield)" - }, - { - "id": "of_flask_recovery_applied_instantly", - "pattern": "(\\d+)% of flask recovery applied instantly", - "vendor_pattern": "(%d+)%% of flask recovery applied instantly", - "mods": [ - { - "name": "FlaskInstantRecovery", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:3463(探针求值自动转写;样本:20% of Flask Recovery applied Instantly)" - }, - { - "id": "passives_in_radius_can_be_allocated_without_being_connected_to_your_tr", - "pattern": "passives in radius can be allocated without being connected to your tree", - "vendor_pattern": "passives in radius can be allocated without being connected to your tree", - "mods": [ - { - "name": "JewelData", - "type": "LIST", - "value": { - "key": "intuitiveLeapLike", - "value": true - } - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:5467(探针求值自动转写;样本:Passives in Radius can be Allocated without being connected to your tree)" - }, - { - "id": "reduced_movement_speed_penalty_from_using_skills_while_moving", - "pattern": "(\\d+)% reduced movement speed penalty from using skills while moving", - "vendor_pattern": "(%d+)%% reduced movement speed penalty from using skills while moving", - "mods": [ - { - "name": "MovementSpeedPenalty", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": false, - "batch": "S1", - "source_note": "ModParser.lua:6017(探针求值自动转写;样本:5% reduced Movement Speed Penalty from using Skills while moving)" - }, - { - "id": "take_no_extra_damage_from_critical_hits", - "pattern": "take no extra damage from critical hits", - "vendor_pattern": "take no extra damage from critical hits", - "mods": [ - { - "name": "ReduceCritExtraDamage", - "type": "BASE", - "value": 100.0 - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:6129(暴击不造成额外伤害 → ReduceCritExtraDamage BASE 100;pobr 准源 legacy.rs:1445)" - }, - { - "id": "targets_can_be_affected_by_poisons", - "pattern": "targets can be affected by \\+(\\d+) of your poisons at the same time", - "vendor_pattern": "targets can be affected by %+(%d+) of your poisons at the same time", - "mods": [ - { - "name": "PoisonCanStack", - "type": "FLAG", - "value": true - }, - { - "name": "PoisonStacks", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:3895(+N poison stacks → PoisonCanStack FLAG + PoisonStacks BASE N;pobr 准源 legacy.rs:1974)" - }, - { - "id": "you_have_no_mana", - "pattern": "you have no mana", - "vendor_pattern": "you have no mana", - "mods": [ - { - "name": "MaximumMana", - "type": "OVERRIDE", - "value": 0.0 - } - ], - "verified": false, - "batch": "S0", - "source_note": "pobr 准源 mod_parser.rs:529-531;vendor ModParser.lua:2392(ModName 为 Mana,pobr 现行稳定名 MaximumMana)" - }, - { - "id": "your_critical_damage_bonus_override", - "pattern": "your critical damage bonus is (\\d+(?:\\.\\d+)?)%", - "vendor_pattern": "your critical damage bonus is (%d+)%%", - "mods": [ - { - "name": "CriticalStrikeMultiplier", - "type": "OVERRIDE", - "value": "$1" - } - ], - "verified": false, - "batch": "S0", - "source_note": "pobr 准源 mod_parser.rs:490-506(dynamic_overrides);vendor ModParser.lua:5798 同语义但 ModName 为 CritMultiplier(pobr 现行稳定名 CriticalStrikeMultiplier,B-3 双跑以 pobr 现状为基准)" - }, - { - "id": "your_critical_hit_chance_override", - "pattern": "your critical hit chance is (\\d+(?:\\.\\d+)?)%", - "mods": [ - { - "name": "CriticalStrikeChance", - "type": "OVERRIDE", - "value": "$1" - } - ], - "verified": false, - "batch": "S0", - "source_note": "pobr 准源 mod_parser.rs:490-506;vendor 无数值形同 key(仅 lucky 系 :3730-3732)" - }, - { - "id": "stun_threshold_from_es_8", - "pattern": "gain additional stun threshold equal to 8% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 8 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "stun_threshold_from_es_12", - "pattern": "gain additional stun threshold equal to 12% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 12 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "stun_threshold_from_es_15", - "pattern": "gain additional stun threshold equal to 15% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 15 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "stun_threshold_from_es_20", - "pattern": "gain additional stun threshold equal to 20% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 20 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "stun_threshold_from_es_30", - "pattern": "gain additional stun threshold equal to 30% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 30 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "ailment_threshold_from_es_8", - "pattern": "gain additional ailment threshold equal to 8% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 8 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "ailment_threshold_from_es_12", - "pattern": "gain additional ailment threshold equal to 12% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 12 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "ailment_threshold_from_es_15", - "pattern": "gain additional ailment threshold equal to 15% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 15 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "ailment_threshold_from_es_20", - "pattern": "gain additional ailment threshold equal to 20% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 20 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "ailment_threshold_from_es_30", - "pattern": "gain additional ailment threshold equal to 30% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ - { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 30 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "mana_regen_from_life_6", - "pattern": "regenerate mana equal to 6% of maximum life per second", - "vendor_pattern": "regenerate mana equal to (%d+)%% of maximum life per second", - "mods": [ - { - "name": "ManaRegen", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "Life", - "percent": 6 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #28(oracle:ManaRegen BASE 1 + PercentStat{Life,6});DSL PercentStat 字面值——按语料数值钉 6" - }, - { - "id": "ailment_threshold_from_evasion_100", - "pattern": "gain 100% of evasion rating as extra ailment threshold", - "vendor_pattern": "gain (%d+)%% of evasion rating as extra ailment threshold", - "mods": [ - { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "Evasion", - "percent": 100 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #24(oracle:AilmentThreshold BASE 1 + PercentStat{Evasion,100});玩家侧 AilmentThreshold 暂无 calc 消费——解析对齐 vendor,接线即生效" - }, - { - "id": "threshold_10_red_support_gems_5pct", - "pattern": "5% increased maximum life if you have at least 10 red support gems socketed", - "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", - "mods": [ - { - "name": "Life", - "type": "INC", - "value": 5, - "tags": [ - { - "type": "MultiplierThreshold", - "var": "RedSupportGems", - "threshold": 10 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" - }, - { - "id": "threshold_10_blue_support_gems_5pct", - "pattern": "5% increased maximum mana if you have at least 10 blue support gems socketed", - "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", - "mods": [ - { - "name": "Mana", - "type": "INC", - "value": 5, - "tags": [ - { - "type": "MultiplierThreshold", - "var": "BlueSupportGems", - "threshold": 10 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" - }, - { - "id": "threshold_10_green_support_gems_5pct", - "pattern": "5% increased movement speed if you have at least 10 green support gems socketed", - "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", - "mods": [ - { - "name": "MovementSpeed", - "type": "INC", - "value": 5, - "tags": [ - { - "type": "MultiplierThreshold", - "var": "GreenSupportGems", - "threshold": 10 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" - }, - { - "id": "body_armour_grants_reduced_crit_damage_bonus_100", - "pattern": "body armour grants hits against you have 100% reduced critical damage bonus", - "vendor_pattern": "body armour grants hits against you have (%d+)%% reduced critical damage bonus", - "mods": [ - { - "name": "ReduceCritExtraDamage", - "type": "BASE", - "value": 100 - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3284(mod(ReduceCritExtraDamage, BASE, num, ItemCondition{Body Armour, rarityCond=NORMAL});ItemCondition 无 PoBR 落点按 body-armour-grants pre_flag 族同口径丢弃(dropped-tag 报表 7 行同款欠条)——Smith of Kitava 机制要求白装胸甲,语料 build 满足故不 over-apply。语料数值集 {100} 钉值。消费方 defence.rs CritExtraDamageReduction 已存在" - }, - { - "id": "body_armour_grants_unaffected_by_damaging_ailments", - "pattern": "body armour grants unaffected by damaging ailments", - "vendor_pattern": "body armour grants unaffected by damaging ailments", - "mods": [ - { - "name": "SelfBleedEffect", - "type": "MORE", - "value": -100 - }, - { - "name": "SelfIgniteEffect", - "type": "MORE", - "value": -100 - }, - { - "name": "SelfPoisonEffect", - "type": "MORE", - "value": -100 - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3288(Self{Bleed,Ignite,Poison}Effect MORE -100 ×3,ItemCondition 同上丢弃)。Self*Effect 玩家侧 calc 暂无消费(解析对齐 vendor,自异常效果消费端接线时词条已就位;AilmentThreshold 先例)" - }, - { - "id": "break_armour_on_crit_with_spells", - "pattern": "break armour on critical hit with spells equal to (\\d+)% of physical damage dealt", - "vendor_pattern": "break armour on critical hit with spells equal to (%d+)%% of physical damage dealt", - "mods": [ - { - "name": "Condition:CanArmourBreak", - "type": "FLAG", - "value": true, - "tags": [ - { - "type": "Condition", - "neg": true, - "var": "NeverCrit" - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3803(flag(Condition:CanArmourBreak, GlobalEffect{Buff, ArmourBreak}, Condition{neg NeverCrit});数值不进 mods——vendor 同样忽略捕获,正则一条覆盖语料 {5,10}。GlobalEffect 无 PoBR 落点丢弃(已有评估:不值窄做);CanArmourBreak 玩家侧 calc 暂无消费(armour break 机制未建模,解析对齐 vendor)" - }, - { - "id": "limit_for_elemental_skills", - "pattern": "\\+(\\d+) to limit for elemental skills", - "vendor_pattern": "%+(%d+) to limit for elemental skills", - "mods": [ - { - "name": "AdditionalCooldownUses", - "type": "BASE", - "value": "$1", - "tags": [ - { - "type": "SkillType", - "skillTypeList": [ - "Cold", - "Fire", - "Lightning" - ] - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3374(mod(AdditionalCooldownUses, BASE, num, SkillType{skillTypeList={Cold,Fire,Lightning}});skillTypeList → SkillTypes 位集并集(intersects 即 OR,与 vendor ModStore SkillType 任一命中同语义)。消费方 skill_mechanics.rs stored uses 已存在)" - }, - { - "id": "immune_to_ignite", - "pattern": "immun[ei]t?y? to ignite", - "vendor_pattern": "immun[ei]t?y? to ignite", - "mods": [ - { - "name": "IgniteImmune", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5172 flag(IgniteImmune);charm『Immune to Ignite』词条走此行" - }, - { - "id": "immune_to_freeze", - "pattern": "immun[ei]t?y? to freeze", - "vendor_pattern": "immun[ei]t?y? to freeze", - "mods": [ - { - "name": "FreezeImmune", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S2", - "source_note": "ModParser.lua:5168 flag(FreezeImmune);Thawing Charm『Immune to Freeze』词条走此行" - }, - { - "id": "recover_mana_when_used", - "pattern": "recovers? (\\d+) mana when used", - "vendor_pattern": null, - "mods": [ - { - "name": "ManaOnFlaskUse", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S0", - "source_note": "药剂/护符使用时瞬时回魔:解析为 ManaOnFlaskUse BASE(归因/Calcs 可见;稳态面板无消费——瞬时恢复不属于稳态模型,flask 恢复面板落地时接线)。vendor 走 flaskData 本地系统不产 mod" - }, - { - "id": "flask_mana_recovery_overflow", - "pattern": "mana recovery from flasks can overflow maximum mana( during effect)?", - "vendor_pattern": null, - "mods": [ - { - "name": "FlaskManaRecoveryOverflow", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S0", - "source_note": "Melting Maelstrom:效果期间药剂魔力恢复可溢出上限 → FLAG 进 ModDb(归因可见;溢出魔力稳态无消费者,vendor 亦未建模)" - }, - { - "id": "possessed_by_spirit_on_use", - "pattern": "possessed by [a-z' ]+ for (\\d+) seconds on use", - "vendor_pattern": null, - "mods": [ - { - "name": "SpiritPossessionOnUse", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "S0", - "source_note": "金护符阿兹莫里之灵附身(Stag/Wolf/Bear…):玩家侧 buff 数值无可验证一手来源(poe2db/wiki 仅载敌方附身效果,BuffDefinitions 未入库)→ 暂解析为 FLAG 登记,数据可得后升级 handler 接具体 buff" - }, - { - "id": "also_grants_guard", - "pattern": "also grants (\\d+) guard", - "vendor_pattern": null, - "mods": [ - { - "name": "GuardAbsorbRate", - "type": "BASE", - "value": 100 - }, - { - "name": "GuardAbsorbLimit", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S0", - "source_note": "药剂/护符使用效果:PoE2 Guard buff=生命/ES 前全额吸收击中的缓冲池(agent-docs/active-defences.md §2;poe2db Guard 定义)→ rate 100 + limit $1,消费端 pool_setup.rs:132 现成(EHP/MaxHit 承受层)。vendor ModParser 未接此词条(比 PoB2 多建模一层)" - }, - { - "id": "armour_applies_to_fcl_instead_of_physical", - "pattern": "armour applies to fire, cold and lightning damage taken from hits instead of physical damage", - "vendor_pattern": "armour applies to fire, cold and lightning damage taken from hits instead of physical damage", - "mods": [ - { - "name": "ArmourAppliesToFireDamageTaken", - "type": "BASE", - "value": 100 - }, - { - "name": "ArmourAppliesToColdDamageTaken", - "type": "BASE", - "value": 100 - }, - { - "name": "ArmourAppliesToLightningDamageTaken", - "type": "BASE", - "value": 100 - }, - { - "name": "ArmourDoesNotApplyToPhysicalDamageTaken", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:2545-2550(三元素 ArmourAppliesToDamageTaken BASE 100 + instead 变体专属 flag)。消费端 taken.rs armour_applies_pct(flag 时物理份额归 0)。legacy-only 测试掩盖的 engine 缺口(原 mod_parser_m2_defence.rs:182)" - }, - { - "id": "pct_of_armour_applies_to_fcl", - "pattern": "(\\d+)% of armour applies to fire, cold and lightning damage taken from hits", - "vendor_pattern": "(%d+)%% of armour applies to fire, cold and lightning damage taken from hits", - "mods": [ - { - "name": "ArmourAppliesToFireDamageTaken", - "type": "BASE", - "value": "$1" - }, - { - "name": "ArmourAppliesToColdDamageTaken", - "type": "BASE", - "value": "$1" - }, - { - "name": "ArmourAppliesToLightningDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:2551-2555(function(num)→三元素 BASE num)。消费端 taken.rs armour_applies_pct。原 mod_parser_m2_defence.rs:192" - }, - { - "id": "armour_applies_to_elemental_damage", - "pattern": "armour applies to elemental damage", - "vendor_pattern": "armour applies to elemental damage", - "mods": [ - { - "name": "ArmourAppliesToFireDamageTaken", - "type": "BASE", - "value": 100 - }, - { - "name": "ArmourAppliesToColdDamageTaken", - "type": "BASE", - "value": 100 - }, - { - "name": "ArmourAppliesToLightningDamageTaken", - "type": "BASE", - "value": 100 - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:2556-2560(三元素 BASE 100,无 instead flag)。原 mod_parser_m2_defence.rs:201" - }, - { - "id": "pct_of_armour_applies_to_elemental_damage", - "pattern": "\\+?(\\d+)% of armour (?:also )?applies to elemental damage", - "vendor_pattern": "%+?(%d+)%% of armour (a?l?s?o? ?)applies to elemental damage", - "mods": [ - { - "name": "ArmourAppliesToFireDamageTaken", - "type": "BASE", - "value": "$1" - }, - { - "name": "ArmourAppliesToColdDamageTaken", - "type": "BASE", - "value": "$1" - }, - { - "name": "ArmourAppliesToLightningDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:2561-2565(function(num)→三元素 BASE num;vendor 第二捕获 also 为可选填充词,此处转非捕获组保持 $1 语义)" - }, - { - "id": "energy_shield_protects_mana_instead_of_life", - "pattern": "energy shield protects mana instead of life", - "vendor_pattern": "energy shield protects mana instead of life", - "mods": [ - { - "name": "EnergyShieldProtectsMana", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:2465(Eldritch Battery 类)。消费端 keystone_registry.rs DefenceKeystones.energy_shield_protects_mana → pool_damage.rs/ehp.rs/defence.rs。原 mod_parser_m2_defence.rs:99" - }, - { - "id": "converts_all_evasion_rating_to_armour", - "pattern": "converts all evasion rating to armour", - "vendor_pattern": "converts all evasion rating to armour", - "mods": [ - { - "name": "IronReflexes", - "type": "FLAG", - "value": true - }, - { - "name": "EvasionConvertToArmour", - "type": "BASE", - "value": 100 - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:2369(Iron Reflexes 类:flag + EvasionConvertToArmour BASE 100)。消费端 defence.rs calc_defence_resources 五元 ConvertTo 矩阵 + keystone_registry.rs iron_reflexes(Unbreakable 联动)。原 mod_parser_m2_defence.rs:265" - }, - { - "id": "chance_to_deflect_is_lucky", - "pattern": "chance to deflect is lucky", - "vendor_pattern": "chance to deflect is lucky", - "mods": [ - { - "name": "DeflectIsLucky", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:4202。消费端 defence_panels.rs:298 偏斜几率 lucky 幂(1−(1−p)²)。原 mod_parser_m2_defence.rs:397" - }, - { - "id": "chance_to_block_damage_is_lucky", - "pattern": "chance to block damage is lucky", - "vendor_pattern": "chance to block damage is lucky", - "mods": [ - { - "name": "BlockChanceIsLucky", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:4371。消费端 defence_panels.rs:174 effective(BlockChance) luck_transform" - }, - { - "id": "chance_to_block_is_lucky", - "pattern": "(?:your )?chance to block is lucky", - "vendor_pattern": "y?o?u?r? ?chance to block is lucky", - "mods": [ - { - "name": "BlockChanceIsLucky", - "type": "FLAG", - "value": true - }, - { - "name": "ProjectileBlockChanceIsLucky", - "type": "FLAG", - "value": true - }, - { - "name": "SpellBlockChanceIsLucky", - "type": "FLAG", - "value": true - }, - { - "name": "SpellProjectileBlockChanceIsLucky", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "wave2-defence", - "source_note": "ModParser.lua:4372(四 flag 全套)。消费端 defence_panels.rs:174 消费 BlockChance/SpellBlockChance 两口径;Projectile/SpellProjectile 两 flag 目前无消费端(pobr 未算 projectile block 有效值),形态先对齐 vendor" - }, - { - "id": "bonded_modifiers_enabler", - "pattern": "gain the benefits of bonded modifiers on runes and idols", - "vendor_pattern": "gain the benefits of bonded modifiers on runes and idols", - "mods": [ - { - "name": "Condition:CanUseBondedModifiers", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "M6-3b", - "source_note": "ModParser.lua:3418-3419(Druid Oracle specialModList 整行;flag(Condition:CanUseBondedModifiers) 解锁 'Bonded:' 前缀词条,vendor :1437)" - }, - { - "id": "bonded_meta_res_eff_inc", - "pattern": "bonded: meta skills have (\\d+)% increased reservation efficiency", - "vendor_pattern": "(Bonded 前缀 + 域限定效率;见 source_note 口径说明)", - "mods": [ - { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "Condition", - "var": "CanUseBondedModifiers" - }, - { - "type": "SkillType", - "skillType": "Meta" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "Bonded rune 变体(语料 C1 双跑对齐:legacy 的 bonded 递归会命中 parse_reservation_efficiency,引擎须同产出;Condition:CanUseBondedModifiers 默认不激活)。vendor 对前缀域形 Bonded 变体 unsupported(special 表不在前缀剥离后重查)——此处取 legacy 超集方向(条件门控恒 false 时零行为)" - }, - { - "id": "bonded_herald_res_eff_inc", - "pattern": "bonded: (\\d+)% increased reservation efficiency of herald skills", - "vendor_pattern": "(Bonded 前缀 + 域限定效率;见 source_note 口径说明)", - "mods": [ - { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "Condition", - "var": "CanUseBondedModifiers" - }, - { - "type": "SkillType", - "skillType": "Herald" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "Bonded rune 变体(语料 C1 双跑对齐:legacy 的 bonded 递归会命中 parse_reservation_efficiency,引擎须同产出;Condition:CanUseBondedModifiers 默认不激活)。vendor 对前缀域形 Bonded 变体 unsupported(special 表不在前缀剥离后重查)——此处取 legacy 超集方向(条件门控恒 false 时零行为)" - }, - { - "id": "all_elemental_damage_converted_to_chaos", - "pattern": "all elemental damage converted to chaos damage", - "vendor_pattern": "all elemental damage converted to chaos damage", - "mods": [ - { - "name": "ColdDamageConvertToChaos", - "type": "BASE", - "value": 100.0 - }, - { - "name": "FireDamageConvertToChaos", - "type": "BASE", - "value": 100.0 - }, - { - "name": "LightningDamageConvertToChaos", - "type": "BASE", - "value": 100.0 - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:2410-2414(all elemental → chaos 100,整行特例;通用后缀路径整行不识别)" - }, - { - "id": "cold_damage_taken_as_lightning", - "pattern": "(\\d+(?:\\.\\d+)?)% of cold damage taken as lightning", - "vendor_pattern": "(%d+)%% of cold damage taken as lightning", - "mods": [ - { - "name": "ColdDamageTakenAsLightning", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5655(裸目标 taken-as:suffixTypes 无 bare `as lightning`,通用路径残留 `as lightning` 并误产 ColdDamageTaken BASE)" - }, - { - "id": "fire_and_lightning_from_hits_taken_as_cold_during_effect", - "pattern": "(\\d+(?:\\.\\d+)?)% of fire and lightning damage from hits taken as cold damage during effect", - "vendor_pattern": "(%d+)%% of fire and lightning damage from hits taken as cold damage during effect", - "mods": [ - { - "name": "FireDamageFromHitsTakenAsCold", - "type": "BASE", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "UsingFlask" - } - ] - }, - { - "name": "LightningDamageFromHitsTakenAsCold", - "type": "BASE", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "UsingFlask" - } - ] - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5657-5660(flask 词条;通用路径只吃 lightning 分支、残留 `Fire and`)" - }, - { - "id": "fire_damage_taken_as_lightning", - "pattern": "(\\d+(?:\\.\\d+)?)% of fire damage taken as lightning", - "vendor_pattern": "(%d+)%% of fire damage taken as lightning", - "mods": [ - { - "name": "FireDamageTakenAsLightning", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5656(同 cold_damage_taken_as_lightning,裸目标 taken-as)" - }, - { - "id": "phys_cold_lightning_converted_to_fire", - "pattern": "(\\d+(?:\\.\\d+)?)% of physical, cold and lightning damage converted to fire damage", - "vendor_pattern": "(%d+)%% of physical, cold and lightning damage converted to fire damage", - "mods": [ - { - "name": "PhysicalDamageConvertToFire", - "type": "BASE", - "value": "$1" - }, - { - "name": "LightningDamageConvertToFire", - "type": "BASE", - "value": "$1" - }, - { - "name": "ColdDamageConvertToFire", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:2405-2409(多源合写 converted to:通用路径只吃 lightning 分支、残留 `Physical, Cold and`)" - }, - { - "id": "phys_from_hits_taken_as_random_element", - "pattern": "(\\d+(?:\\.\\d+)?)% of physical damage from hits taken as damage of a random element", - "vendor_pattern": "(%d+)%% of physical damage from hits taken as damage of a random element", - "mods": [ - { - "name": "PhysicalDamageFromHitsTakenAsFire", - "type": "BASE", - "value": { - "ref": "$1", - "ops": [ - { - "div": 3.0 - } - ] - } - }, - { - "name": "PhysicalDamageFromHitsTakenAsCold", - "type": "BASE", - "value": { - "ref": "$1", - "ops": [ - { - "div": 3.0 - } - ] - } - }, - { - "name": "PhysicalDamageFromHitsTakenAsLightning", - "type": "BASE", - "value": { - "ref": "$1", - "ops": [ - { - "div": 3.0 - } - ] - } - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5661-5665(AVERAGE 三分口径 num/3;真实语料词条,legacy 删除后回归恢复)" - }, - { - "id": "eternal_life_while_energy_shield", - "pattern": "your life cannot change while you have energy shield", - "vendor_pattern": "your life cannot change while you have energy shield", - "mods": [ - { - "name": "EternalLife", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:3144(-- Lich specialModList,flag(EternalLife));legacy 经 max-hit ES bypass 消费(pool_damage.rs)。fork-a 迁入补 engine 数据驱动 special 通道缺口(detonate-dead MaxHit/EHP 偏低)" - }, - { - "id": "blood_mage_additional_life_from_body_es", - "pattern": "gain additional maximum life equal to 100% of the (?:item )?energy shield on equipped body armour", - "vendor_pattern": "gain additional maximum life equal to (%d+)%% of the (i?t?e?m? ?)energy shield on equipped body armour", - "mods": [ - { - "name": "MaximumLife", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "Multiplier", - "var": "EnergyShieldOnbodyarmour", - "div": 1 - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:2834(-- Blood Mage specialModList,mod(Life,BASE,1,PerStat{stat=EnergyShieldOnBody Armour,div=100/N});legacy.rs:1796-1810 同形(MaximumLife BASE 1 × Multiplier:EnergyShieldOnbodyarmour,div=100/N,slot 倍率经 orchestrator per_slot_defence_multipliers 填充 = Body Armour 件级最终 ES)。本条钉 N=100(升华恒 100%,corpus 唯一形)→ div=1;special 整行命中在 pre_flag 剥 `gain` 前。需 special_mod.rs 字面 Multiplier tag 支持。fork-a 补 engine special 通道(blood-mage Life→EHP/MaxHit 级联)" - }, - { - "id": "life_loss_from_hits_prevented_over_4s", - "pattern": "(\\d+(?:\\.\\d+)?)% of life loss from hits is prevented, then that much life is lost over 4 seconds instead", - "vendor_pattern": "(%d+)%% of life loss from hits is prevented, then that much life is lost over 4 seconds instead", - "mods": [ - { - "name": "LifeLossPrevented", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:2839 function(num)→mod(LifeLossPrevented,BASE,num);legacy.rs:1824-1827 同。pool_setup.rs:104 `min(ΣLifeLossPrevented,100)` 减伤(max-hit 机制)。vendor 函数形,extract 抽不出静态模板 → 引擎误解析为 MaximumLife BASE 25。fork-a 补 engine special(blood-mage EHP/五类 MaxHit 0.88x)" - }, - { - "id": "fire_damage_while_affected_by_herald_of_ash", - "pattern": "(\\d+(?:\\.\\d+)?)% increased fire damage while affected by herald of ash", - "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2536 硬编码]", - "mods": [ - { - "name": "FireDamage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "AffectedByHeraldofAsh" - }, - { - "type": "DamageType", - "damageType": "Fire" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "vendor `while affected by (%a+)` modTagList 单词捕获抽不出多词「Herald of Ash」+ 多元素 varList 引擎丢弃 → engine 无条件应用、泄漏进 dot(grenade/ice-shot dot over-count)。legacy.rs:2536 硬编码 `AffectedByHeraldofAsh`。整行 data 化对齐 legacy(FireDamage INC + Condition + DamageType)" - }, - { - "id": "cold_damage_while_affected_by_herald_of_ice", - "pattern": "(\\d+(?:\\.\\d+)?)% increased cold damage while affected by herald of ice", - "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2532 硬编码]", - "mods": [ - { - "name": "ColdDamage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "AffectedByHeraldofIce" - }, - { - "type": "DamageType", - "damageType": "Cold" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "同 fire_damage_while_affected_by_herald_of_ash;legacy.rs:2532 `AffectedByHeraldofIce`" - }, - { - "id": "lightning_damage_while_affected_by_herald_of_thunder", - "pattern": "(\\d+(?:\\.\\d+)?)% increased lightning damage while affected by herald of thunder", - "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2540 硬编码]", - "mods": [ - { - "name": "LightningDamage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "AffectedByHeraldofThunder" - }, - { - "type": "DamageType", - "damageType": "Lightning" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "同 fire_damage_while_affected_by_herald_of_ash;legacy.rs:2540 `AffectedByHeraldofThunder`" - }, - { - "id": "body_armour_grants_of_armour_also_applies_to_chaos_damage", - "pattern": "body armour grants \\+?(\\d+)% of armour also applies to (chaos) damage", - "vendor_pattern": "body armour grants %+(%d+)%% of armour also applies to (%a+) damage", - "mods": [ - { - "name": "ArmourAppliesToChaosDamageTaken", - "type": "BASE", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "NormalBodyArmourEquipped" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:3281 -- Warrior - Smith of Kitava,function(num,_,dmgType)→mod(ArmourAppliesToDamageTaken,BASE,num,{type=ItemCondition,itemSlot=Body Armour,rarityCond=NORMAL})。vendor 函数形带 (%a+) 伤害类型捕获 + ItemCondition,extract_parser_rules 抽不出(S2 批仅手转非 body-armour 的 fire/cold/lightning 三元素,漏 chaos + 漏 body-armour-grants 变体)。legacy 经 body-armour-grants 前缀剥离 + parse_armour_applies_pct(legacy.rs:393/1621)通用处理任意类型,chaos 正常,故 engine 落后 legacy。本条把 vendor ItemCondition{Body Armour,NORMAL} 映射为 pobr `Condition:NormalBodyArmourEquipped`(orchestrator calc_orchestrator.rs:373 按体甲槽 Normal 稀有度置真,与 legacy.rs:397 同口径),消费侧 taken.rs ArmourAppliesToChaosDamageTaken。修 warrior-smith-of-kitava-shield-wall ChaosMaxHit 0.84x→1.00x" - }, - { - "id": "prevent_pct_of_damage_from_deflected_hits", - "pattern": "prevent \\+(\\d+(?:\\.\\d+)?)% of damage from deflected hits", - "vendor_pattern": "prevent %+(%d+)%% of damage from deflected hits", - "mods": [ - { - "name": "DeflectEffect", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:4191 function(num)→mod(DeflectEffect,BASE,num);legacy.rs:1721 parse_deflection_special 同。defence_panels.rs:241 偏斜减伤=基础40+ΣBASE DeflectEffect,clamp[0,100](DeflectChance 58% 时生效,非仅≥100)。vendor 函数形 extract 抽不出 → 引擎丢弃(twister 缺 DeflectEffect 3+6=9 → 偏斜减伤 40 而非 49 → EHP 0.89x)。捕获形一条匹配全部 N。fork-a 补 engine special(monk-martial-artist-twister TotalEHP)" - }, - { - "id": "you_can_apply_an_additional_curse", - "pattern": "you can apply an additional curse", - "vendor_pattern": "you can apply an additional curse", - "mods": [ - { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": 1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4303(Whispers of Doom keystone tree 47759 第一行『You can apply an additional Curse』;第二行 Double Activation Delay 已在 double_activation_delay_of_curses 条目)。缺它时 frost-bomb 第二诅咒 Elemental Weakness 被挤出唯一槽,敌侧 -45 元素抗丢失(CritChance 路径外的 hit 伤害缺口)" - }, - { - "id": "enemies_can_have_1_additional_curse", - "pattern": "enemies can have 1 additional curse", - "vendor_pattern": "enemies can have 1 additional curse", - "mods": [ - { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": 1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4302(curse limit 平表同族)" - }, - { - "id": "you_can_apply_one_fewer_curse", - "pattern": "you can apply one fewer curse", - "vendor_pattern": "you can apply one fewer curse", - "mods": [ - { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": -1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4306(curse limit 平表同族)" - }, - { - "id": "you_can_apply_an_additional_mark", - "pattern": "you can apply an additional mark", - "vendor_pattern": "you can apply an additional mark", - "mods": [ - { - "name": "EnemyMarkLimit", - "type": "BASE", - "value": 1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4309(mark limit 平表同族)" - }, - { - "id": "es_added_to_armour_for_pdr", - "pattern": "(\\d+(?:\\.\\d+)?)% of your current energy shield is added to your armour for determining your physical damage reduction from armour", - "vendor_pattern": "(%d+)%% of your current energy shield is added to your armour for determining your physical damage reduction from armour", - "mods": [ - { - "name": "EnergyShieldAppliesToPhysicalDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:2917-2919(Disciple of Varashta 升华 Sacred Rituals,tree node 56857 —— N% of your current Energy Shield is added to your Armour for determining your Physical Damage Reduction from Armour → EnergyShieldAppliesToPhysicalDamageTaken BASE N)。消费 taken.rs effective_applied_armour 的 from_es 借入项(CalcDefence.lua:2358-2367)。pobr 准源 legacy.rs parse_defence_numeric_sentence(须走 legacy 以过 collect.rs::combine_wrapped_then_filter 树 stat 闸门)。" - }, - { - "id": "take_pct_less_damage", - "pattern": "take (\\d+)% less damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% less Damage)", - "mods": [ - { - "name": "DamageTaken", - "type": "MORE", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" - }, - { - "id": "take_pct_more_damage", - "pattern": "take (\\d+)% more damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% more Damage)", - "mods": [ - { - "name": "DamageTaken", - "type": "MORE", - "value": { - "ref": "$1" - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" - }, - { - "id": "take_pct_reduced_damage", - "pattern": "take (\\d+)% reduced damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% reduced Damage)", - "mods": [ - { - "name": "DamageTaken", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" - }, - { - "id": "take_pct_increased_damage", - "pattern": "take (\\d+)% increased damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% increased Damage)", - "mods": [ - { - "name": "DamageTaken", - "type": "INC", - "value": { - "ref": "$1" - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" - }, - { - "id": "meta_skills_res_eff_inc", - "pattern": "meta skills have (\\d+)% increased reservation efficiency", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ - { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "SkillType", - "skillType": "Meta" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" - }, - { - "id": "meta_skills_res_eff_red", - "pattern": "meta skills have (\\d+)% reduced reservation efficiency", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ - { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - }, - "tags": [ - { - "type": "SkillType", - "skillType": "Meta" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" - }, - { - "id": "herald_res_eff_inc", - "pattern": "(\\d+)% increased reservation efficiency of herald skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ - { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "SkillType", - "skillType": "Herald" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" - }, - { - "id": "herald_res_eff_red", - "pattern": "(\\d+)% reduced reservation efficiency of herald skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ - { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - }, - "tags": [ - { - "type": "SkillType", - "skillType": "Herald" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" - }, - { - "id": "spirit_res_eff_inc", - "pattern": "(\\d+)% increased spirit reservation efficiency of skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ - { - "name": "SpiritReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" - }, - { - "id": "spirit_res_eff_red", - "pattern": "(\\d+)% reduced spirit reservation efficiency of skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ - { - "name": "SpiritReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" - }, - { - "id": "totems_reserve_spirit_each", - "pattern": "totems reserve (\\d+) spirit each", - "vendor_pattern": "(ModParser.lua:4644 Ancestral Bond;run-parsemod 实证双 mod)", - "mods": [ - { - "name": "AncestralBond", - "type": "FLAG", - "value": true - }, - { - "name": "ExtraSpirit", - "type": "BASE", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "SkillType", - "skillType": "SummonsTotem" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 45202「Ancestral Bond」Totems reserve 75 Spirit each;消费=spirit_reservation_modifiers totem 入选分支(SummonsTotem×AncestralBond flag,CalcDefence.lua:197)+ ExtraSpirit per-gem Sum;legacy parse_totem_spirit_reservation 同形过树闸门" - }, - { - "id": "banner_aura_magnitudes_inc", - "pattern": "banner skills have (\\d+)% increased aura magnitudes", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", - "mods": [ - { - "name": "AuraEffect", - "type": "INC", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "SkillType", - "skillType": "Banner" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" - }, - { - "id": "aura_skills_magnitudes_inc", - "pattern": "aura skills have (\\d+)% increased magnitudes", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", - "mods": [ - { - "name": "Magnitude", - "type": "INC", - "value": { - "ref": "$1" - }, - "tags": [ - { - "type": "SkillType", - "skillType": "Aura" - } - ] - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" - }, - { - "id": "aura_magnitudes_bare_inc", - "pattern": "(\\d+)% increased aura magnitudes", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", - "mods": [ - { - "name": "AuraEffect", - "type": "INC", - "value": { - "ref": "$1" - } - } - ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" + "batch": "wave2-defence", + "source_note": "ModParser.lua:4372(四 flag 全套)。消费端 defence_panels.rs:174 消费 BlockChance/SpellBlockChance 两口径;Projectile/SpellProjectile 两 flag 目前无消费端(pobr 未算 projectile block 有效值),形态先对齐 vendor" } ] } diff --git a/data/4.5.0.3.4/overlay/special_mods.json b/data/overlay-common/special_mods.json similarity index 100% rename from data/4.5.0.3.4/overlay/special_mods.json rename to data/overlay-common/special_mods.json index 9f257610..13ae0afe 100644 --- a/data/4.5.0.3.4/overlay/special_mods.json +++ b/data/overlay-common/special_mods.json @@ -11,169 +11,6 @@ "regen_command": "(手工策展域:本字段记录对账命令而非再生命令)sync-pob-catalog check --special-coverage(M5b A-3 落地后启用)" }, "entries": [ - { - "id": "eternal_life_while_energy_shield", - "pattern": "your life cannot change while you have energy shield", - "vendor_pattern": "your life cannot change while you have energy shield", - "mods": [ - { - "name": "EternalLife", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:3144(-- Lich specialModList,flag(EternalLife));legacy 经 max-hit ES bypass 消费(pool_damage.rs)。fork-a 迁入补 engine 数据驱动 special 通道缺口(detonate-dead MaxHit/EHP 偏低)" - }, - { - "id": "blood_mage_additional_life_from_body_es", - "pattern": "gain additional maximum life equal to 100% of the (?:item )?energy shield on equipped body armour", - "vendor_pattern": "gain additional maximum life equal to (%d+)%% of the (i?t?e?m? ?)energy shield on equipped body armour", - "mods": [ - { - "name": "MaximumLife", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "Multiplier", - "var": "EnergyShieldOnbodyarmour", - "div": 1 - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:2834(-- Blood Mage specialModList,mod(Life,BASE,1,PerStat{stat=EnergyShieldOnBody Armour,div=100/N});legacy.rs:1796-1810 同形(MaximumLife BASE 1 × Multiplier:EnergyShieldOnbodyarmour,div=100/N,slot 倍率经 orchestrator per_slot_defence_multipliers 填充 = Body Armour 件级最终 ES)。本条钉 N=100(升华恒 100%,corpus 唯一形)→ div=1;special 整行命中在 pre_flag 剥 `gain` 前。需 special_mod.rs 字面 Multiplier tag 支持。fork-a 补 engine special 通道(blood-mage Life→EHP/MaxHit 级联)" - }, - { - "id": "life_loss_from_hits_prevented_over_4s", - "pattern": "(\\d+(?:\\.\\d+)?)% of life loss from hits is prevented, then that much life is lost over 4 seconds instead", - "vendor_pattern": "(%d+)%% of life loss from hits is prevented, then that much life is lost over 4 seconds instead", - "mods": [ - { - "name": "LifeLossPrevented", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:2839 function(num)→mod(LifeLossPrevented,BASE,num);legacy.rs:1824-1827 同。pool_setup.rs:104 `min(ΣLifeLossPrevented,100)` 减伤(max-hit 机制)。vendor 函数形,extract 抽不出静态模板 → 引擎误解析为 MaximumLife BASE 25。fork-a 补 engine special(blood-mage EHP/五类 MaxHit 0.88x)" - }, - { - "id": "fire_damage_while_affected_by_herald_of_ash", - "pattern": "(\\d+(?:\\.\\d+)?)% increased fire damage while affected by herald of ash", - "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2536 硬编码]", - "mods": [ - { - "name": "FireDamage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "AffectedByHeraldofAsh" - }, - { - "type": "DamageType", - "damageType": "Fire" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "vendor `while affected by (%a+)` modTagList 单词捕获抽不出多词「Herald of Ash」+ 多元素 varList 引擎丢弃 → engine 无条件应用、泄漏进 dot(grenade/ice-shot dot over-count)。legacy.rs:2536 硬编码 `AffectedByHeraldofAsh`。整行 data 化对齐 legacy(FireDamage INC + Condition + DamageType)" - }, - { - "id": "cold_damage_while_affected_by_herald_of_ice", - "pattern": "(\\d+(?:\\.\\d+)?)% increased cold damage while affected by herald of ice", - "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2532 硬编码]", - "mods": [ - { - "name": "ColdDamage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "AffectedByHeraldofIce" - }, - { - "type": "DamageType", - "damageType": "Cold" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "同 fire_damage_while_affected_by_herald_of_ash;legacy.rs:2532 `AffectedByHeraldofIce`" - }, - { - "id": "lightning_damage_while_affected_by_herald_of_thunder", - "pattern": "(\\d+(?:\\.\\d+)?)% increased lightning damage while affected by herald of thunder", - "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2540 硬编码]", - "mods": [ - { - "name": "LightningDamage", - "type": "INC", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "AffectedByHeraldofThunder" - }, - { - "type": "DamageType", - "damageType": "Lightning" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "同 fire_damage_while_affected_by_herald_of_ash;legacy.rs:2540 `AffectedByHeraldofThunder`" - }, - { - "id": "body_armour_grants_of_armour_also_applies_to_chaos_damage", - "pattern": "body armour grants \\+?(\\d+)% of armour also applies to (chaos) damage", - "vendor_pattern": "body armour grants %+(%d+)%% of armour also applies to (%a+) damage", - "mods": [ - { - "name": "ArmourAppliesToChaosDamageTaken", - "type": "BASE", - "value": "$1", - "tags": [ - { - "type": "Condition", - "var": "NormalBodyArmourEquipped" - } - ] - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:3281 -- Warrior - Smith of Kitava,function(num,_,dmgType)→mod(ArmourAppliesToDamageTaken,BASE,num,{type=ItemCondition,itemSlot=Body Armour,rarityCond=NORMAL})。vendor 函数形带 (%a+) 伤害类型捕获 + ItemCondition,extract_parser_rules 抽不出(S2 批仅手转非 body-armour 的 fire/cold/lightning 三元素,漏 chaos + 漏 body-armour-grants 变体)。legacy 经 body-armour-grants 前缀剥离 + parse_armour_applies_pct(legacy.rs:393/1621)通用处理任意类型,chaos 正常,故 engine 落后 legacy。本条把 vendor ItemCondition{Body Armour,NORMAL} 映射为 pobr `Condition:NormalBodyArmourEquipped`(orchestrator calc_orchestrator.rs:373 按体甲槽 Normal 稀有度置真,与 legacy.rs:397 同口径),消费侧 taken.rs ArmourAppliesToChaosDamageTaken。修 warrior-smith-of-kitava-shield-wall ChaosMaxHit 0.84x→1.00x" - }, - { - "id": "prevent_pct_of_damage_from_deflected_hits", - "pattern": "prevent \\+(\\d+(?:\\.\\d+)?)% of damage from deflected hits", - "vendor_pattern": "prevent %+(%d+)%% of damage from deflected hits", - "mods": [ - { - "name": "DeflectEffect", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "fork-a", - "source_note": "ModParser.lua:4191 function(num)→mod(DeflectEffect,BASE,num);legacy.rs:1721 parse_deflection_special 同。defence_panels.rs:241 偏斜减伤=基础40+ΣBASE DeflectEffect,clamp[0,100](DeflectChance 58% 时生效,非仅≥100)。vendor 函数形 extract 抽不出 → 引擎丢弃(twister 缺 DeflectEffect 3+6=9 → 偏斜减伤 40 而非 49 → EHP 0.89x)。捕获形一条匹配全部 N。fork-a 补 engine special(monk-martial-artist-twister TotalEHP)" - }, { "id": "all_damage_is_taken_from_mana_before_life", "pattern": "all damage is taken from mana before life", @@ -532,66 +369,6 @@ "batch": "S2", "source_note": "ModParser.lua:4275(探针求值自动转写;样本:Curses you inflict ignore Curse limit)" }, - { - "id": "you_can_apply_an_additional_curse", - "pattern": "you can apply an additional curse", - "vendor_pattern": "you can apply an additional curse", - "mods": [ - { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": 1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4303(Whispers of Doom keystone tree 47759 第一行『You can apply an additional Curse』;第二行 Double Activation Delay 已在 double_activation_delay_of_curses 条目)。缺它时 frost-bomb 第二诅咒 Elemental Weakness 被挤出唯一槽,敌侧 -45 元素抗丢失(CritChance 路径外的 hit 伤害缺口)" - }, - { - "id": "enemies_can_have_1_additional_curse", - "pattern": "enemies can have 1 additional curse", - "vendor_pattern": "enemies can have 1 additional curse", - "mods": [ - { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": 1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4302(curse limit 平表同族)" - }, - { - "id": "you_can_apply_one_fewer_curse", - "pattern": "you can apply one fewer curse", - "vendor_pattern": "you can apply one fewer curse", - "mods": [ - { - "name": "EnemyCurseLimit", - "type": "BASE", - "value": -1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4306(curse limit 平表同族)" - }, - { - "id": "you_can_apply_an_additional_mark", - "pattern": "you can apply an additional mark", - "vendor_pattern": "you can apply an additional mark", - "mods": [ - { - "name": "EnemyMarkLimit", - "type": "BASE", - "value": 1 - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:4309(mark limit 平表同族)" - }, { "id": "defend_with_pct_of_armour", "pattern": "defend with (\\d+(?:\\.\\d+)?)% of armour(?: during effect)?", @@ -614,21 +391,6 @@ "batch": "S2", "source_note": "ModParser.lua:2616/:2619(Defend with N% of Armour [during effect] → ArmourDefense BASE N-100;pobr 准源 legacy.rs:1715。during effect 变体同无条件,消费侧 max_of)" }, - { - "id": "es_added_to_armour_for_pdr", - "pattern": "(\\d+(?:\\.\\d+)?)% of your current energy shield is added to your armour for determining your physical damage reduction from armour", - "vendor_pattern": "(%d+)%% of your current energy shield is added to your armour for determining your physical damage reduction from armour", - "mods": [ - { - "name": "EnergyShieldAppliesToPhysicalDamageTaken", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "hand-curated", - "source_note": "ModParser.lua:2917-2919(Disciple of Varashta 升华 Sacred Rituals,tree node 56857 —— N% of your current Energy Shield is added to your Armour for determining your Physical Damage Reduction from Armour → EnergyShieldAppliesToPhysicalDamageTaken BASE N)。消费 taken.rs effective_applied_armour 的 from_es 借入项(CalcDefence.lua:2358-2367)。pobr 准源 legacy.rs parse_defence_numeric_sentence(须走 legacy 以过 collect.rs::combine_wrapped_then_filter 树 stat 闸门)。" - }, { "id": "double_activation_delay_of_curses", "pattern": "double activation delay of curses", @@ -1691,958 +1453,1196 @@ "source_note": "pobr 准源 mod_parser.rs:490-506;vendor 无数值形同 key(仅 lucky 系 :3730-3732)" }, { - "id": "bonded_modifiers_enabler", - "pattern": "gain the benefits of bonded modifiers on runes and idols", - "vendor_pattern": "gain the benefits of bonded modifiers on runes and idols", + "id": "stun_threshold_from_es_8", + "pattern": "gain additional stun threshold equal to 8% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "Condition:CanUseBondedModifiers", - "type": "FLAG", - "value": true - } - ], - "verified": false, - "batch": "M6-3b", - "source_note": "ModParser.lua:3418-3419(Druid Oracle specialModList 整行;flag(Condition:CanUseBondedModifiers) 解锁 'Bonded:' 前缀词条,vendor :1437)" - }, - { - "id": "take_pct_less_damage", - "pattern": "take (\\d+)% less damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% less Damage)", - "mods": [ - { - "name": "DamageTaken", - "type": "MORE", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } + "name": "StunThreshold", + "type": "BASE", + "value": 1, + "tags": [ + { + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 8 + } + ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "take_pct_more_damage", - "pattern": "take (\\d+)% more damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% more Damage)", + "id": "stun_threshold_from_es_12", + "pattern": "gain additional stun threshold equal to 12% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "DamageTaken", - "type": "MORE", - "value": { - "ref": "$1" - } + "name": "StunThreshold", + "type": "BASE", + "value": 1, + "tags": [ + { + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 12 + } + ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "take_pct_reduced_damage", - "pattern": "take (\\d+)% reduced damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% reduced Damage)", + "id": "stun_threshold_from_es_15", + "pattern": "gain additional stun threshold equal to 15% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "DamageTaken", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } + "name": "StunThreshold", + "type": "BASE", + "value": 1, + "tags": [ + { + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 15 + } + ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "take_pct_increased_damage", - "pattern": "take (\\d+)% increased damage", - "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% increased Damage)", + "id": "stun_threshold_from_es_20", + "pattern": "gain additional stun threshold equal to 20% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "DamageTaken", - "type": "INC", - "value": { - "ref": "$1" - } + "name": "StunThreshold", + "type": "BASE", + "value": 1, + "tags": [ + { + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 20 + } + ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "meta_skills_res_eff_inc", - "pattern": "meta skills have (\\d+)% increased reservation efficiency", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "id": "stun_threshold_from_es_30", + "pattern": "gain additional stun threshold equal to 30% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, + "name": "StunThreshold", + "type": "BASE", + "value": 1, "tags": [ { - "type": "SkillType", - "skillType": "Meta" + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 30 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "meta_skills_res_eff_red", - "pattern": "meta skills have (\\d+)% reduced reservation efficiency", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "id": "ailment_threshold_from_es_8", + "pattern": "gain additional ailment threshold equal to 8% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - }, + "name": "AilmentThreshold", + "type": "BASE", + "value": 1, "tags": [ { - "type": "SkillType", - "skillType": "Meta" + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 8 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "herald_res_eff_inc", - "pattern": "(\\d+)% increased reservation efficiency of herald skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "id": "ailment_threshold_from_es_12", + "pattern": "gain additional ailment threshold equal to 12% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, + "name": "AilmentThreshold", + "type": "BASE", + "value": 1, "tags": [ { - "type": "SkillType", - "skillType": "Herald" + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 12 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "herald_res_eff_red", - "pattern": "(\\d+)% reduced reservation efficiency of herald skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "id": "ailment_threshold_from_es_15", + "pattern": "gain additional ailment threshold equal to 15% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - }, + "name": "AilmentThreshold", + "type": "BASE", + "value": 1, "tags": [ { - "type": "SkillType", - "skillType": "Herald" + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 15 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "spirit_res_eff_inc", - "pattern": "(\\d+)% increased spirit reservation efficiency of skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "id": "ailment_threshold_from_es_20", + "pattern": "gain additional ailment threshold equal to 20% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", "mods": [ { - "name": "SpiritReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - } + "name": "AilmentThreshold", + "type": "BASE", + "value": 1, + "tags": [ + { + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 20 + } + ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "spirit_res_eff_red", - "pattern": "(\\d+)% reduced spirit reservation efficiency of skills", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", - "mods": [ + "id": "ailment_threshold_from_es_30", + "pattern": "gain additional ailment threshold equal to 30% of maximum energy shield", + "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "mods": [ { - "name": "SpiritReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1", - "ops": [ - { - "negate": {} - } - ] - } + "name": "AilmentThreshold", + "type": "BASE", + "value": 1, + "tags": [ + { + "type": "PercentStat", + "stat": "EnergyShield", + "percent": 30 + } + ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" }, { - "id": "totems_reserve_spirit_each", - "pattern": "totems reserve (\\d+) spirit each", - "vendor_pattern": "(ModParser.lua:4644 Ancestral Bond;run-parsemod 实证双 mod)", + "id": "mana_regen_from_life_6", + "pattern": "regenerate mana equal to 6% of maximum life per second", + "vendor_pattern": "regenerate mana equal to (%d+)%% of maximum life per second", "mods": [ { - "name": "AncestralBond", - "type": "FLAG", - "value": true - }, - { - "name": "ExtraSpirit", + "name": "ManaRegen", "type": "BASE", - "value": { - "ref": "$1" - }, + "value": 1, "tags": [ { - "type": "SkillType", - "skillType": "SummonsTotem" + "type": "PercentStat", + "stat": "Life", + "percent": 6 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "tree node 45202「Ancestral Bond」Totems reserve 75 Spirit each;消费=spirit_reservation_modifiers totem 入选分支(SummonsTotem×AncestralBond flag,CalcDefence.lua:197)+ ExtraSpirit per-gem Sum;legacy parse_totem_spirit_reservation 同形过树闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "A2 真缺口 #28(oracle:ManaRegen BASE 1 + PercentStat{Life,6});DSL PercentStat 字面值——按语料数值钉 6" }, { - "id": "bonded_meta_res_eff_inc", - "pattern": "bonded: meta skills have (\\d+)% increased reservation efficiency", - "vendor_pattern": "(Bonded 前缀 + 域限定效率;见 source_note 口径说明)", + "id": "ailment_threshold_from_evasion_100", + "pattern": "gain 100% of evasion rating as extra ailment threshold", + "vendor_pattern": "gain (%d+)%% of evasion rating as extra ailment threshold", "mods": [ { - "name": "ReservationEfficiency", - "type": "INC", - "value": { - "ref": "$1" - }, + "name": "AilmentThreshold", + "type": "BASE", + "value": 1, "tags": [ { - "type": "Condition", - "var": "CanUseBondedModifiers" - }, - { - "type": "SkillType", - "skillType": "Meta" + "type": "PercentStat", + "stat": "Evasion", + "percent": 100 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "Bonded rune 变体(语料 C1 双跑对齐:legacy 的 bonded 递归会命中 parse_reservation_efficiency,引擎须同产出;Condition:CanUseBondedModifiers 默认不激活)。vendor 对前缀域形 Bonded 变体 unsupported(special 表不在前缀剥离后重查)——此处取 legacy 超集方向(条件门控恒 false 时零行为)" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "A2 真缺口 #24(oracle:AilmentThreshold BASE 1 + PercentStat{Evasion,100});玩家侧 AilmentThreshold 暂无 calc 消费——解析对齐 vendor,接线即生效" }, { - "id": "bonded_herald_res_eff_inc", - "pattern": "bonded: (\\d+)% increased reservation efficiency of herald skills", - "vendor_pattern": "(Bonded 前缀 + 域限定效率;见 source_note 口径说明)", + "id": "threshold_10_red_support_gems_5pct", + "pattern": "5% increased maximum life if you have at least 10 red support gems socketed", + "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", "mods": [ { - "name": "ReservationEfficiency", + "name": "Life", "type": "INC", - "value": { - "ref": "$1" - }, + "value": 5, "tags": [ { - "type": "Condition", - "var": "CanUseBondedModifiers" - }, - { - "type": "SkillType", - "skillType": "Herald" + "type": "MultiplierThreshold", + "var": "RedSupportGems", + "threshold": 10 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "Bonded rune 变体(语料 C1 双跑对齐:legacy 的 bonded 递归会命中 parse_reservation_efficiency,引擎须同产出;Condition:CanUseBondedModifiers 默认不激活)。vendor 对前缀域形 Bonded 变体 unsupported(special 表不在前缀剥离后重查)——此处取 legacy 超集方向(条件门控恒 false 时零行为)" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" }, { - "id": "banner_aura_magnitudes_inc", - "pattern": "banner skills have (\\d+)% increased aura magnitudes", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", + "id": "threshold_10_blue_support_gems_5pct", + "pattern": "5% increased maximum mana if you have at least 10 blue support gems socketed", + "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", "mods": [ { - "name": "AuraEffect", + "name": "Mana", "type": "INC", - "value": { - "ref": "$1" - }, + "value": 5, "tags": [ { - "type": "SkillType", - "skillType": "Banner" + "type": "MultiplierThreshold", + "var": "BlueSupportGems", + "threshold": 10 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" }, { - "id": "aura_skills_magnitudes_inc", - "pattern": "aura skills have (\\d+)% increased magnitudes", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", + "id": "threshold_10_green_support_gems_5pct", + "pattern": "5% increased movement speed if you have at least 10 green support gems socketed", + "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", "mods": [ { - "name": "Magnitude", + "name": "MovementSpeed", "type": "INC", - "value": { - "ref": "$1" - }, + "value": 5, "tags": [ { - "type": "SkillType", - "skillType": "Aura" + "type": "MultiplierThreshold", + "var": "GreenSupportGems", + "threshold": 10 } ] } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" }, { - "id": "aura_magnitudes_bare_inc", - "pattern": "(\\d+)% increased aura magnitudes", - "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", + "id": "body_armour_grants_reduced_crit_damage_bonus_100", + "pattern": "body armour grants hits against you have 100% reduced critical damage bonus", + "vendor_pattern": "body armour grants hits against you have (%d+)%% reduced critical damage bonus", "mods": [ { - "name": "AuraEffect", - "type": "INC", - "value": { - "ref": "$1" - } + "name": "ReduceCritExtraDamage", + "type": "BASE", + "value": 100 } ], - "verified": true, - "batch": "hand-curated", - "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" + "verified": false, + "batch": "a2-real-gaps", + "source_note": "ModParser.lua:3284(mod(ReduceCritExtraDamage, BASE, num, ItemCondition{Body Armour, rarityCond=NORMAL});ItemCondition 无 PoBR 落点按 body-armour-grants pre_flag 族同口径丢弃(dropped-tag 报表 7 行同款欠条)——Smith of Kitava 机制要求白装胸甲,语料 build 满足故不 over-apply。语料数值集 {100} 钉值。消费方 defence.rs CritExtraDamageReduction 已存在" }, { - "id": "stun_threshold_from_es_8", - "pattern": "gain additional stun threshold equal to 8% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "body_armour_grants_unaffected_by_damaging_ailments", + "pattern": "body armour grants unaffected by damaging ailments", + "vendor_pattern": "body armour grants unaffected by damaging ailments", "mods": [ { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 8 - } - ] + "name": "SelfBleedEffect", + "type": "MORE", + "value": -100 + }, + { + "name": "SelfIgniteEffect", + "type": "MORE", + "value": -100 + }, + { + "name": "SelfPoisonEffect", + "type": "MORE", + "value": -100 } ], "verified": false, "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "source_note": "ModParser.lua:3288(Self{Bleed,Ignite,Poison}Effect MORE -100 ×3,ItemCondition 同上丢弃)。Self*Effect 玩家侧 calc 暂无消费(解析对齐 vendor,自异常效果消费端接线时词条已就位;AilmentThreshold 先例)" }, { - "id": "stun_threshold_from_es_12", - "pattern": "gain additional stun threshold equal to 12% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "break_armour_on_crit_with_spells", + "pattern": "break armour on critical hit with spells equal to (\\d+)% of physical damage dealt", + "vendor_pattern": "break armour on critical hit with spells equal to (%d+)%% of physical damage dealt", "mods": [ { - "name": "StunThreshold", - "type": "BASE", - "value": 1, + "name": "Condition:CanArmourBreak", + "type": "FLAG", + "value": true, "tags": [ { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 12 + "type": "Condition", + "neg": true, + "var": "NeverCrit" } ] } ], "verified": false, "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "source_note": "ModParser.lua:3803(flag(Condition:CanArmourBreak, GlobalEffect{Buff, ArmourBreak}, Condition{neg NeverCrit});数值不进 mods——vendor 同样忽略捕获,正则一条覆盖语料 {5,10}。GlobalEffect 无 PoBR 落点丢弃(已有评估:不值窄做);CanArmourBreak 玩家侧 calc 暂无消费(armour break 机制未建模,解析对齐 vendor)" }, { - "id": "stun_threshold_from_es_15", - "pattern": "gain additional stun threshold equal to 15% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "limit_for_elemental_skills", + "pattern": "\\+(\\d+) to limit for elemental skills", + "vendor_pattern": "%+(%d+) to limit for elemental skills", "mods": [ { - "name": "StunThreshold", + "name": "AdditionalCooldownUses", "type": "BASE", - "value": 1, + "value": "$1", "tags": [ { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 15 + "type": "SkillType", + "skillTypeList": [ + "Cold", + "Fire", + "Lightning" + ] } ] } ], "verified": false, "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "source_note": "ModParser.lua:3374(mod(AdditionalCooldownUses, BASE, num, SkillType{skillTypeList={Cold,Fire,Lightning}});skillTypeList → SkillTypes 位集并集(intersects 即 OR,与 vendor ModStore SkillType 任一命中同语义)。消费方 skill_mechanics.rs stored uses 已存在)" }, { - "id": "stun_threshold_from_es_20", - "pattern": "gain additional stun threshold equal to 20% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ + "id": "bonded_modifiers_enabler", + "pattern": "gain the benefits of bonded modifiers on runes and idols", + "vendor_pattern": "gain the benefits of bonded modifiers on runes and idols", + "mods": [ { - "name": "StunThreshold", - "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 20 - } - ] + "name": "Condition:CanUseBondedModifiers", + "type": "FLAG", + "value": true } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "batch": "M6-3b", + "source_note": "ModParser.lua:3418-3419(Druid Oracle specialModList 整行;flag(Condition:CanUseBondedModifiers) 解锁 'Bonded:' 前缀词条,vendor :1437)" }, { - "id": "stun_threshold_from_es_30", - "pattern": "gain additional stun threshold equal to 30% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "bonded_meta_res_eff_inc", + "pattern": "bonded: meta skills have (\\d+)% increased reservation efficiency", + "vendor_pattern": "(Bonded 前缀 + 域限定效率;见 source_note 口径说明)", "mods": [ { - "name": "StunThreshold", - "type": "BASE", - "value": 1, + "name": "ReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1" + }, "tags": [ { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 30 + "type": "Condition", + "var": "CanUseBondedModifiers" + }, + { + "type": "SkillType", + "skillType": "Meta" } ] } ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "verified": true, + "batch": "hand-curated", + "source_note": "Bonded rune 变体(语料 C1 双跑对齐:legacy 的 bonded 递归会命中 parse_reservation_efficiency,引擎须同产出;Condition:CanUseBondedModifiers 默认不激活)。vendor 对前缀域形 Bonded 变体 unsupported(special 表不在前缀剥离后重查)——此处取 legacy 超集方向(条件门控恒 false 时零行为)" }, { - "id": "ailment_threshold_from_es_8", - "pattern": "gain additional ailment threshold equal to 8% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "bonded_herald_res_eff_inc", + "pattern": "bonded: (\\d+)% increased reservation efficiency of herald skills", + "vendor_pattern": "(Bonded 前缀 + 域限定效率;见 source_note 口径说明)", "mods": [ { - "name": "AilmentThreshold", - "type": "BASE", - "value": 1, + "name": "ReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1" + }, "tags": [ { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 8 + "type": "Condition", + "var": "CanUseBondedModifiers" + }, + { + "type": "SkillType", + "skillType": "Herald" } ] } ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "verified": true, + "batch": "hand-curated", + "source_note": "Bonded rune 变体(语料 C1 双跑对齐:legacy 的 bonded 递归会命中 parse_reservation_efficiency,引擎须同产出;Condition:CanUseBondedModifiers 默认不激活)。vendor 对前缀域形 Bonded 变体 unsupported(special 表不在前缀剥离后重查)——此处取 legacy 超集方向(条件门控恒 false 时零行为)" }, { - "id": "ailment_threshold_from_es_12", - "pattern": "gain additional ailment threshold equal to 12% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "all_elemental_damage_converted_to_chaos", + "pattern": "all elemental damage converted to chaos damage", + "vendor_pattern": "all elemental damage converted to chaos damage", "mods": [ { - "name": "AilmentThreshold", + "name": "ColdDamageConvertToChaos", "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 12 - } - ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "ailment_threshold_from_es_15", - "pattern": "gain additional ailment threshold equal to 15% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", - "mods": [ + "value": 100.0 + }, { - "name": "AilmentThreshold", + "name": "FireDamageConvertToChaos", "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 15 - } - ] + "value": 100.0 + }, + { + "name": "LightningDamageConvertToChaos", + "type": "BASE", + "value": 100.0 } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "batch": "conv-taken-as", + "source_note": "ModParser.lua:2410-2414(all elemental → chaos 100,整行特例;通用后缀路径整行不识别)" }, { - "id": "ailment_threshold_from_es_20", - "pattern": "gain additional ailment threshold equal to 20% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "cold_damage_taken_as_lightning", + "pattern": "(\\d+(?:\\.\\d+)?)% of cold damage taken as lightning", + "vendor_pattern": "(%d+)%% of cold damage taken as lightning", "mods": [ { - "name": "AilmentThreshold", + "name": "ColdDamageTakenAsLightning", "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 20 - } - ] + "value": "$1" } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" + "batch": "conv-taken-as", + "source_note": "ModParser.lua:5655(裸目标 taken-as:suffixTypes 无 bare `as lightning`,通用路径残留 `as lightning` 并误产 ColdDamageTaken BASE)" }, { - "id": "ailment_threshold_from_es_30", - "pattern": "gain additional ailment threshold equal to 30% of maximum energy shield", - "vendor_pattern": "gain additional (%a+) threshold equal to (%d+)%% of maximum energy shield", + "id": "fire_and_lightning_from_hits_taken_as_cold_during_effect", + "pattern": "(\\d+(?:\\.\\d+)?)% of fire and lightning damage from hits taken as cold damage during effect", + "vendor_pattern": "(%d+)%% of fire and lightning damage from hits taken as cold damage during effect", "mods": [ { - "name": "AilmentThreshold", + "name": "FireDamageFromHitsTakenAsCold", "type": "BASE", - "value": 1, + "value": "$1", "tags": [ { - "type": "PercentStat", - "stat": "EnergyShield", - "percent": 30 + "type": "Condition", + "var": "UsingFlask" } ] - } - ], - "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:5234(mod(firstToUpper(statType)..\"Threshold\", BASE, 1, PercentStat{EnergyShield, num});DSL PercentStat 为字面值不吃捕获——按语料数值集 {8,12,15,20,30} 钉值展开(blood_mage_additional_life_from_body_es 同款先例)。A2 真缺口榜 #2/#3(hit=6,cnt=20/16)。AilmentThreshold 玩家侧 calc 暂无消费(解析对齐 vendor,消费端接线时词条已就位)" - }, - { - "id": "mana_regen_from_life_6", - "pattern": "regenerate mana equal to 6% of maximum life per second", - "vendor_pattern": "regenerate mana equal to (%d+)%% of maximum life per second", - "mods": [ + }, { - "name": "ManaRegen", + "name": "LightningDamageFromHitsTakenAsCold", "type": "BASE", - "value": 1, + "value": "$1", "tags": [ { - "type": "PercentStat", - "stat": "Life", - "percent": 6 + "type": "Condition", + "var": "UsingFlask" } ] } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #28(oracle:ManaRegen BASE 1 + PercentStat{Life,6});DSL PercentStat 字面值——按语料数值钉 6" + "batch": "conv-taken-as", + "source_note": "ModParser.lua:5657-5660(flask 词条;通用路径只吃 lightning 分支、残留 `Fire and`)" }, { - "id": "ailment_threshold_from_evasion_100", - "pattern": "gain 100% of evasion rating as extra ailment threshold", - "vendor_pattern": "gain (%d+)%% of evasion rating as extra ailment threshold", + "id": "fire_damage_taken_as_lightning", + "pattern": "(\\d+(?:\\.\\d+)?)% of fire damage taken as lightning", + "vendor_pattern": "(%d+)%% of fire damage taken as lightning", "mods": [ { - "name": "AilmentThreshold", + "name": "FireDamageTakenAsLightning", "type": "BASE", - "value": 1, - "tags": [ - { - "type": "PercentStat", - "stat": "Evasion", - "percent": 100 - } - ] + "value": "$1" } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #24(oracle:AilmentThreshold BASE 1 + PercentStat{Evasion,100});玩家侧 AilmentThreshold 暂无 calc 消费——解析对齐 vendor,接线即生效" + "batch": "conv-taken-as", + "source_note": "ModParser.lua:5656(同 cold_damage_taken_as_lightning,裸目标 taken-as)" }, { - "id": "threshold_10_red_support_gems_5pct", - "pattern": "5% increased maximum life if you have at least 10 red support gems socketed", - "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", + "id": "phys_cold_lightning_converted_to_fire", + "pattern": "(\\d+(?:\\.\\d+)?)% of physical, cold and lightning damage converted to fire damage", + "vendor_pattern": "(%d+)%% of physical, cold and lightning damage converted to fire damage", "mods": [ { - "name": "Life", - "type": "INC", - "value": 5, - "tags": [ - { - "type": "MultiplierThreshold", - "var": "RedSupportGems", - "threshold": 10 - } - ] + "name": "PhysicalDamageConvertToFire", + "type": "BASE", + "value": "$1" + }, + { + "name": "LightningDamageConvertToFire", + "type": "BASE", + "value": "$1" + }, + { + "name": "ColdDamageConvertToFire", + "type": "BASE", + "value": "$1" } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" + "batch": "conv-taken-as", + "source_note": "ModParser.lua:2405-2409(多源合写 converted to:通用路径只吃 lightning 分支、残留 `Physical, Cold and`)" }, { - "id": "threshold_10_blue_support_gems_5pct", - "pattern": "5% increased maximum mana if you have at least 10 blue support gems socketed", - "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", + "id": "phys_from_hits_taken_as_random_element", + "pattern": "(\\d+(?:\\.\\d+)?)% of physical damage from hits taken as damage of a random element", + "vendor_pattern": "(%d+)%% of physical damage from hits taken as damage of a random element", "mods": [ { - "name": "Mana", - "type": "INC", - "value": 5, - "tags": [ - { - "type": "MultiplierThreshold", - "var": "BlueSupportGems", - "threshold": 10 - } - ] + "name": "PhysicalDamageFromHitsTakenAsFire", + "type": "BASE", + "value": { + "ref": "$1", + "ops": [ + { + "div": 3.0 + } + ] + } + }, + { + "name": "PhysicalDamageFromHitsTakenAsCold", + "type": "BASE", + "value": { + "ref": "$1", + "ops": [ + { + "div": 3.0 + } + ] + } + }, + { + "name": "PhysicalDamageFromHitsTakenAsLightning", + "type": "BASE", + "value": { + "ref": "$1", + "ops": [ + { + "div": 3.0 + } + ] + } } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" + "batch": "conv-taken-as", + "source_note": "ModParser.lua:5661-5665(AVERAGE 三分口径 num/3;真实语料词条,legacy 删除后回归恢复)" }, { - "id": "threshold_10_green_support_gems_5pct", - "pattern": "5% increased movement speed if you have at least 10 green support gems socketed", - "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", + "id": "eternal_life_while_energy_shield", + "pattern": "your life cannot change while you have energy shield", + "vendor_pattern": "your life cannot change while you have energy shield", "mods": [ { - "name": "MovementSpeed", - "type": "INC", - "value": 5, - "tags": [ - { - "type": "MultiplierThreshold", - "var": "GreenSupportGems", - "threshold": 10 - } - ] + "name": "EternalLife", + "type": "FLAG", + "value": true } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" + "batch": "fork-a", + "source_note": "ModParser.lua:3144(-- Lich specialModList,flag(EternalLife));legacy 经 max-hit ES bypass 消费(pool_damage.rs)。fork-a 迁入补 engine 数据驱动 special 通道缺口(detonate-dead MaxHit/EHP 偏低)" }, { - "id": "body_armour_grants_reduced_crit_damage_bonus_100", - "pattern": "body armour grants hits against you have 100% reduced critical damage bonus", - "vendor_pattern": "body armour grants hits against you have (%d+)%% reduced critical damage bonus", + "id": "blood_mage_additional_life_from_body_es", + "pattern": "gain additional maximum life equal to 100% of the (?:item )?energy shield on equipped body armour", + "vendor_pattern": "gain additional maximum life equal to (%d+)%% of the (i?t?e?m? ?)energy shield on equipped body armour", "mods": [ { - "name": "ReduceCritExtraDamage", + "name": "MaximumLife", "type": "BASE", - "value": 100 + "value": 1, + "tags": [ + { + "type": "Multiplier", + "var": "EnergyShieldOnbodyarmour", + "div": 1 + } + ] } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3284(mod(ReduceCritExtraDamage, BASE, num, ItemCondition{Body Armour, rarityCond=NORMAL});ItemCondition 无 PoBR 落点按 body-armour-grants pre_flag 族同口径丢弃(dropped-tag 报表 7 行同款欠条)——Smith of Kitava 机制要求白装胸甲,语料 build 满足故不 over-apply。语料数值集 {100} 钉值。消费方 defence.rs CritExtraDamageReduction 已存在" + "batch": "fork-a", + "source_note": "ModParser.lua:2834(-- Blood Mage specialModList,mod(Life,BASE,1,PerStat{stat=EnergyShieldOnBody Armour,div=100/N});legacy.rs:1796-1810 同形(MaximumLife BASE 1 × Multiplier:EnergyShieldOnbodyarmour,div=100/N,slot 倍率经 orchestrator per_slot_defence_multipliers 填充 = Body Armour 件级最终 ES)。本条钉 N=100(升华恒 100%,corpus 唯一形)→ div=1;special 整行命中在 pre_flag 剥 `gain` 前。需 special_mod.rs 字面 Multiplier tag 支持。fork-a 补 engine special 通道(blood-mage Life→EHP/MaxHit 级联)" }, { - "id": "body_armour_grants_unaffected_by_damaging_ailments", - "pattern": "body armour grants unaffected by damaging ailments", - "vendor_pattern": "body armour grants unaffected by damaging ailments", + "id": "life_loss_from_hits_prevented_over_4s", + "pattern": "(\\d+(?:\\.\\d+)?)% of life loss from hits is prevented, then that much life is lost over 4 seconds instead", + "vendor_pattern": "(%d+)%% of life loss from hits is prevented, then that much life is lost over 4 seconds instead", "mods": [ { - "name": "SelfBleedEffect", - "type": "MORE", - "value": -100 - }, - { - "name": "SelfIgniteEffect", - "type": "MORE", - "value": -100 - }, - { - "name": "SelfPoisonEffect", - "type": "MORE", - "value": -100 + "name": "LifeLossPrevented", + "type": "BASE", + "value": "$1" } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3288(Self{Bleed,Ignite,Poison}Effect MORE -100 ×3,ItemCondition 同上丢弃)。Self*Effect 玩家侧 calc 暂无消费(解析对齐 vendor,自异常效果消费端接线时词条已就位;AilmentThreshold 先例)" + "batch": "fork-a", + "source_note": "ModParser.lua:2839 function(num)→mod(LifeLossPrevented,BASE,num);legacy.rs:1824-1827 同。pool_setup.rs:104 `min(ΣLifeLossPrevented,100)` 减伤(max-hit 机制)。vendor 函数形,extract 抽不出静态模板 → 引擎误解析为 MaximumLife BASE 25。fork-a 补 engine special(blood-mage EHP/五类 MaxHit 0.88x)" }, { - "id": "break_armour_on_crit_with_spells", - "pattern": "break armour on critical hit with spells equal to (\\d+)% of physical damage dealt", - "vendor_pattern": "break armour on critical hit with spells equal to (%d+)%% of physical damage dealt", + "id": "fire_damage_while_affected_by_herald_of_ash", + "pattern": "(\\d+(?:\\.\\d+)?)% increased fire damage while affected by herald of ash", + "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2536 硬编码]", "mods": [ { - "name": "Condition:CanArmourBreak", - "type": "FLAG", - "value": true, + "name": "FireDamage", + "type": "INC", + "value": "$1", "tags": [ { "type": "Condition", - "neg": true, - "var": "NeverCrit" + "var": "AffectedByHeraldofAsh" + }, + { + "type": "DamageType", + "damageType": "Fire" } ] } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3803(flag(Condition:CanArmourBreak, GlobalEffect{Buff, ArmourBreak}, Condition{neg NeverCrit});数值不进 mods——vendor 同样忽略捕获,正则一条覆盖语料 {5,10}。GlobalEffect 无 PoBR 落点丢弃(已有评估:不值窄做);CanArmourBreak 玩家侧 calc 暂无消费(armour break 机制未建模,解析对齐 vendor)" + "batch": "fork-a", + "source_note": "vendor `while affected by (%a+)` modTagList 单词捕获抽不出多词「Herald of Ash」+ 多元素 varList 引擎丢弃 → engine 无条件应用、泄漏进 dot(grenade/ice-shot dot over-count)。legacy.rs:2536 硬编码 `AffectedByHeraldofAsh`。整行 data 化对齐 legacy(FireDamage INC + Condition + DamageType)" }, { - "id": "limit_for_elemental_skills", - "pattern": "\\+(\\d+) to limit for elemental skills", - "vendor_pattern": "%+(%d+) to limit for elemental skills", + "id": "cold_damage_while_affected_by_herald_of_ice", + "pattern": "(\\d+(?:\\.\\d+)?)% increased cold damage while affected by herald of ice", + "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2532 硬编码]", "mods": [ { - "name": "AdditionalCooldownUses", - "type": "BASE", + "name": "ColdDamage", + "type": "INC", "value": "$1", "tags": [ { - "type": "SkillType", - "skillTypeList": [ - "Cold", - "Fire", - "Lightning" - ] + "type": "Condition", + "var": "AffectedByHeraldofIce" + }, + { + "type": "DamageType", + "damageType": "Cold" } ] } ], "verified": false, - "batch": "a2-real-gaps", - "source_note": "ModParser.lua:3374(mod(AdditionalCooldownUses, BASE, num, SkillType{skillTypeList={Cold,Fire,Lightning}});skillTypeList → SkillTypes 位集并集(intersects 即 OR,与 vendor ModStore SkillType 任一命中同语义)。消费方 skill_mechanics.rs stored uses 已存在)" - }, - { - "id": "all_elemental_damage_converted_to_chaos", - "pattern": "all elemental damage converted to chaos damage", - "vendor_pattern": "all elemental damage converted to chaos damage", - "mods": [ - { - "name": "ColdDamageConvertToChaos", - "type": "BASE", - "value": 100.0 - }, - { - "name": "FireDamageConvertToChaos", - "type": "BASE", - "value": 100.0 - }, - { - "name": "LightningDamageConvertToChaos", - "type": "BASE", - "value": 100.0 - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:2410-2414(all elemental → chaos 100,整行特例;通用后缀路径整行不识别)" - }, - { - "id": "cold_damage_taken_as_lightning", - "pattern": "(\\d+(?:\\.\\d+)?)% of cold damage taken as lightning", - "vendor_pattern": "(%d+)%% of cold damage taken as lightning", - "mods": [ - { - "name": "ColdDamageTakenAsLightning", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5655(裸目标 taken-as:suffixTypes 无 bare `as lightning`,通用路径残留 `as lightning` 并误产 ColdDamageTaken BASE)" + "batch": "fork-a", + "source_note": "同 fire_damage_while_affected_by_herald_of_ash;legacy.rs:2532 `AffectedByHeraldofIce`" }, { - "id": "fire_and_lightning_from_hits_taken_as_cold_during_effect", - "pattern": "(\\d+(?:\\.\\d+)?)% of fire and lightning damage from hits taken as cold damage during effect", - "vendor_pattern": "(%d+)%% of fire and lightning damage from hits taken as cold damage during effect", + "id": "lightning_damage_while_affected_by_herald_of_thunder", + "pattern": "(\\d+(?:\\.\\d+)?)% increased lightning damage while affected by herald of thunder", + "vendor_pattern": "while affected by (%a+) [via modTagList; legacy.rs:2540 硬编码]", "mods": [ { - "name": "FireDamageFromHitsTakenAsCold", - "type": "BASE", + "name": "LightningDamage", + "type": "INC", "value": "$1", "tags": [ { "type": "Condition", - "var": "UsingFlask" + "var": "AffectedByHeraldofThunder" + }, + { + "type": "DamageType", + "damageType": "Lightning" } ] - }, + } + ], + "verified": false, + "batch": "fork-a", + "source_note": "同 fire_damage_while_affected_by_herald_of_ash;legacy.rs:2540 `AffectedByHeraldofThunder`" + }, + { + "id": "body_armour_grants_of_armour_also_applies_to_chaos_damage", + "pattern": "body armour grants \\+?(\\d+)% of armour also applies to (chaos) damage", + "vendor_pattern": "body armour grants %+(%d+)%% of armour also applies to (%a+) damage", + "mods": [ { - "name": "LightningDamageFromHitsTakenAsCold", + "name": "ArmourAppliesToChaosDamageTaken", "type": "BASE", "value": "$1", "tags": [ { "type": "Condition", - "var": "UsingFlask" + "var": "NormalBodyArmourEquipped" } ] } ], "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5657-5660(flask 词条;通用路径只吃 lightning 分支、残留 `Fire and`)" + "batch": "fork-a", + "source_note": "ModParser.lua:3281 -- Warrior - Smith of Kitava,function(num,_,dmgType)→mod(ArmourAppliesToDamageTaken,BASE,num,{type=ItemCondition,itemSlot=Body Armour,rarityCond=NORMAL})。vendor 函数形带 (%a+) 伤害类型捕获 + ItemCondition,extract_parser_rules 抽不出(S2 批仅手转非 body-armour 的 fire/cold/lightning 三元素,漏 chaos + 漏 body-armour-grants 变体)。legacy 经 body-armour-grants 前缀剥离 + parse_armour_applies_pct(legacy.rs:393/1621)通用处理任意类型,chaos 正常,故 engine 落后 legacy。本条把 vendor ItemCondition{Body Armour,NORMAL} 映射为 pobr `Condition:NormalBodyArmourEquipped`(orchestrator calc_orchestrator.rs:373 按体甲槽 Normal 稀有度置真,与 legacy.rs:397 同口径),消费侧 taken.rs ArmourAppliesToChaosDamageTaken。修 warrior-smith-of-kitava-shield-wall ChaosMaxHit 0.84x→1.00x" }, { - "id": "fire_damage_taken_as_lightning", - "pattern": "(\\d+(?:\\.\\d+)?)% of fire damage taken as lightning", - "vendor_pattern": "(%d+)%% of fire damage taken as lightning", + "id": "prevent_pct_of_damage_from_deflected_hits", + "pattern": "prevent \\+(\\d+(?:\\.\\d+)?)% of damage from deflected hits", + "vendor_pattern": "prevent %+(%d+)%% of damage from deflected hits", "mods": [ { - "name": "FireDamageTakenAsLightning", + "name": "DeflectEffect", "type": "BASE", "value": "$1" } ], "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5656(同 cold_damage_taken_as_lightning,裸目标 taken-as)" + "batch": "fork-a", + "source_note": "ModParser.lua:4191 function(num)→mod(DeflectEffect,BASE,num);legacy.rs:1721 parse_deflection_special 同。defence_panels.rs:241 偏斜减伤=基础40+ΣBASE DeflectEffect,clamp[0,100](DeflectChance 58% 时生效,非仅≥100)。vendor 函数形 extract 抽不出 → 引擎丢弃(twister 缺 DeflectEffect 3+6=9 → 偏斜减伤 40 而非 49 → EHP 0.89x)。捕获形一条匹配全部 N。fork-a 补 engine special(monk-martial-artist-twister TotalEHP)" }, { - "id": "phys_cold_lightning_converted_to_fire", - "pattern": "(\\d+(?:\\.\\d+)?)% of physical, cold and lightning damage converted to fire damage", - "vendor_pattern": "(%d+)%% of physical, cold and lightning damage converted to fire damage", + "id": "you_can_apply_an_additional_curse", + "pattern": "you can apply an additional curse", + "vendor_pattern": "you can apply an additional curse", "mods": [ { - "name": "PhysicalDamageConvertToFire", + "name": "EnemyCurseLimit", "type": "BASE", - "value": "$1" - }, + "value": 1 + } + ], + "verified": false, + "batch": "hand-curated", + "source_note": "ModParser.lua:4303(Whispers of Doom keystone tree 47759 第一行『You can apply an additional Curse』;第二行 Double Activation Delay 已在 double_activation_delay_of_curses 条目)。缺它时 frost-bomb 第二诅咒 Elemental Weakness 被挤出唯一槽,敌侧 -45 元素抗丢失(CritChance 路径外的 hit 伤害缺口)" + }, + { + "id": "enemies_can_have_1_additional_curse", + "pattern": "enemies can have 1 additional curse", + "vendor_pattern": "enemies can have 1 additional curse", + "mods": [ { - "name": "LightningDamageConvertToFire", + "name": "EnemyCurseLimit", "type": "BASE", - "value": "$1" - }, + "value": 1 + } + ], + "verified": false, + "batch": "hand-curated", + "source_note": "ModParser.lua:4302(curse limit 平表同族)" + }, + { + "id": "you_can_apply_one_fewer_curse", + "pattern": "you can apply one fewer curse", + "vendor_pattern": "you can apply one fewer curse", + "mods": [ { - "name": "ColdDamageConvertToFire", + "name": "EnemyCurseLimit", "type": "BASE", - "value": "$1" + "value": -1 } ], "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:2405-2409(多源合写 converted to:通用路径只吃 lightning 分支、残留 `Physical, Cold and`)" + "batch": "hand-curated", + "source_note": "ModParser.lua:4306(curse limit 平表同族)" }, { - "id": "phys_from_hits_taken_as_random_element", - "pattern": "(\\d+(?:\\.\\d+)?)% of physical damage from hits taken as damage of a random element", - "vendor_pattern": "(%d+)%% of physical damage from hits taken as damage of a random element", + "id": "you_can_apply_an_additional_mark", + "pattern": "you can apply an additional mark", + "vendor_pattern": "you can apply an additional mark", "mods": [ { - "name": "PhysicalDamageFromHitsTakenAsFire", + "name": "EnemyMarkLimit", "type": "BASE", + "value": 1 + } + ], + "verified": false, + "batch": "hand-curated", + "source_note": "ModParser.lua:4309(mark limit 平表同族)" + }, + { + "id": "es_added_to_armour_for_pdr", + "pattern": "(\\d+(?:\\.\\d+)?)% of your current energy shield is added to your armour for determining your physical damage reduction from armour", + "vendor_pattern": "(%d+)%% of your current energy shield is added to your armour for determining your physical damage reduction from armour", + "mods": [ + { + "name": "EnergyShieldAppliesToPhysicalDamageTaken", + "type": "BASE", + "value": "$1" + } + ], + "verified": false, + "batch": "hand-curated", + "source_note": "ModParser.lua:2917-2919(Disciple of Varashta 升华 Sacred Rituals,tree node 56857 —— N% of your current Energy Shield is added to your Armour for determining your Physical Damage Reduction from Armour → EnergyShieldAppliesToPhysicalDamageTaken BASE N)。消费 taken.rs effective_applied_armour 的 from_es 借入项(CalcDefence.lua:2358-2367)。pobr 准源 legacy.rs parse_defence_numeric_sentence(须走 legacy 以过 collect.rs::combine_wrapped_then_filter 树 stat 闸门)。" + }, + { + "id": "take_pct_less_damage", + "pattern": "take (\\d+)% less damage", + "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% less Damage)", + "mods": [ + { + "name": "DamageTaken", + "type": "MORE", "value": { "ref": "$1", "ops": [ { - "div": 3.0 + "negate": {} } ] } - }, + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + }, + { + "id": "take_pct_more_damage", + "pattern": "take (\\d+)% more damage", + "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% more Damage)", + "mods": [ { - "name": "PhysicalDamageFromHitsTakenAsCold", - "type": "BASE", + "name": "DamageTaken", + "type": "MORE", + "value": { + "ref": "$1" + } + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + }, + { + "id": "take_pct_reduced_damage", + "pattern": "take (\\d+)% reduced damage", + "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% reduced Damage)", + "mods": [ + { + "name": "DamageTaken", + "type": "INC", "value": { "ref": "$1", "ops": [ { - "div": 3.0 + "negate": {} } ] } - }, + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + }, + { + "id": "take_pct_increased_damage", + "pattern": "take (\\d+)% increased damage", + "vendor_pattern": "(vendor 通用改写链解析动词前置形;oracle: Take 30% increased Damage)", + "mods": [ { - "name": "PhysicalDamageFromHitsTakenAsLightning", - "type": "BASE", + "name": "DamageTaken", + "type": "INC", + "value": { + "ref": "$1" + } + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "tree node 28153「Phased Form」等动词前置形受伤词条;oracle run-parsemod 实证四方向(less/more→MORE±N、reduced/increased→INC±N);消费 defence.rs taken_mult_for_type_*;legacy parse_defence_numeric_sentence 同形分支过 collect.rs 树闸门" + }, + { + "id": "meta_skills_res_eff_inc", + "pattern": "meta skills have (\\d+)% increased reservation efficiency", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "mods": [ + { + "name": "ReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1" + }, + "tags": [ + { + "type": "SkillType", + "skillType": "Meta" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + }, + { + "id": "meta_skills_res_eff_red", + "pattern": "meta skills have (\\d+)% reduced reservation efficiency", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "mods": [ + { + "name": "ReservationEfficiency", + "type": "INC", "value": { "ref": "$1", "ops": [ { - "div": 3.0 + "negate": {} + } + ] + }, + "tags": [ + { + "type": "SkillType", + "skillType": "Meta" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + }, + { + "id": "herald_res_eff_inc", + "pattern": "(\\d+)% increased reservation efficiency of herald skills", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "mods": [ + { + "name": "ReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1" + }, + "tags": [ + { + "type": "SkillType", + "skillType": "Herald" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + }, + { + "id": "herald_res_eff_red", + "pattern": "(\\d+)% reduced reservation efficiency of herald skills", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "mods": [ + { + "name": "ReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1", + "ops": [ + { + "negate": {} } ] + }, + "tags": [ + { + "type": "SkillType", + "skillType": "Herald" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + }, + { + "id": "spirit_res_eff_inc", + "pattern": "(\\d+)% increased spirit reservation efficiency of skills", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "mods": [ + { + "name": "SpiritReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1" } } ], - "verified": false, - "batch": "conv-taken-as", - "source_note": "ModParser.lua:5661-5665(AVERAGE 三分口径 num/3;真实语料词条,legacy 删除后回归恢复)" + "verified": true, + "batch": "hand-curated", + "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + }, + { + "id": "spirit_res_eff_red", + "pattern": "(\\d+)% reduced spirit reservation efficiency of skills", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证 tag 口径)", + "mods": [ + { + "name": "SpiritReservationEfficiency", + "type": "INC", + "value": { + "ref": "$1", + "ops": [ + { + "negate": {} + } + ] + } + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定预留效率(tree 42245/63236「Meta Skills have N% increased Reservation Efficiency」等);vendor→ReservationEfficiency INC±N+SkillType tag(run-parsemod 实证);消费=spirit_reservation_modifiers 预留公式 /(1+eff/100)(CalcDefence.lua:240-243/:251);legacy parse_reservation_efficiency 同形过树闸门" + }, + { + "id": "totems_reserve_spirit_each", + "pattern": "totems reserve (\\d+) spirit each", + "vendor_pattern": "(ModParser.lua:4644 Ancestral Bond;run-parsemod 实证双 mod)", + "mods": [ + { + "name": "AncestralBond", + "type": "FLAG", + "value": true + }, + { + "name": "ExtraSpirit", + "type": "BASE", + "value": { + "ref": "$1" + }, + "tags": [ + { + "type": "SkillType", + "skillType": "SummonsTotem" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "tree node 45202「Ancestral Bond」Totems reserve 75 Spirit each;消费=spirit_reservation_modifiers totem 入选分支(SummonsTotem×AncestralBond flag,CalcDefence.lua:197)+ ExtraSpirit per-gem Sum;legacy parse_totem_spirit_reservation 同形过树闸门" + }, + { + "id": "banner_aura_magnitudes_inc", + "pattern": "banner skills have (\\d+)% increased aura magnitudes", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", + "mods": [ + { + "name": "AuraEffect", + "type": "INC", + "value": { + "ref": "$1" + }, + "tags": [ + { + "type": "SkillType", + "skillType": "Banner" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" + }, + { + "id": "aura_skills_magnitudes_inc", + "pattern": "aura skills have (\\d+)% increased magnitudes", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", + "mods": [ + { + "name": "Magnitude", + "type": "INC", + "value": { + "ref": "$1" + }, + "tags": [ + { + "type": "SkillType", + "skillType": "Aura" + } + ] + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" + }, + { + "id": "aura_magnitudes_bare_inc", + "pattern": "(\\d+)% increased aura magnitudes", + "vendor_pattern": "(vendor 通用改写链;oracle run-parsemod 实证)", + "mods": [ + { + "name": "AuraEffect", + "type": "INC", + "value": { + "ref": "$1" + } + } + ], + "verified": true, + "batch": "hand-curated", + "source_note": "域限定 buff magnitude(wolf-pack banner/aura 词条族;run-parsemod 实证 tag 口径);消费=buff_pass aura 乘区(AuraEffect 桶×Magnitude 桶分乘,CalcPerform.lua:2204-2205);legacy parse_scoped_buff_magnitude 同形过树/装备闸门" } ] } From cf8b571b9f1e066a00605683b73d346cb366c664 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 00:21:38 +0800 Subject: [PATCH 11/74] feat(pipeline): vendor calc-delta report (P0-2) Add diff-vendor-calcs.sh: compares two PoB2 vendor pins and emits a markdown triage list, turning post-patch engine adaptation into a checklist instead of parity-drop archaeology. Report sections: - Calc module diff: Modules/Calc*.lua + ModParser.lua diffstat + folded hunks (collapse to stat-only past MAX_HUNK_LINES). - Data module diff: Data/ + Modules/Data*.lua ranked by churn, no hunks. - Extracted overlay diff: run extract-lua (special-mods/parser-rules/ uniques) against both pins; entity-level add/remove/change per collection via json_entry_diff.py. Details: - Each pin is a shallow git checkout in .cache/vendor-delta//; codeload tarballs strip the working tree the headless extractor's HeadlessWrapper bootstrap needs (yields "modLib.parseMod missing"). Retries transient fetch EOF; writes a per-pin .pob2-version.txt for extract-lua --version-file. - Soft-degrade (regen-all.sh convention): an extract that fails on the old pin is noted, the report still generates. - Ignore /.cache/; document usage in pipeline/README.md. - Include sample report a82a33b -> ce8bffab (PoB 0.21.0 -> 0.22.0). --- .gitignore | 2 + devs/docs/audits/vendor-delta-ce8bffab.md | 2380 +++++++++++++++++++++ pipeline/README.md | 23 + pipeline/diff-vendor-calcs.sh | 287 +++ pipeline/json_entry_diff.py | 119 ++ 5 files changed, 2811 insertions(+) create mode 100644 devs/docs/audits/vendor-delta-ce8bffab.md create mode 100755 pipeline/diff-vendor-calcs.sh create mode 100755 pipeline/json_entry_diff.py diff --git a/.gitignore b/.gitignore index b4e04136..d40ab2db 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ /target-gate/ /target-master/ /target-dualrun/ +# vendor-delta 报告的 codeload tarball 缓存(pipeline/diff-vendor-calcs.sh) +/.cache/ # 本机相关的 cargo 配置(网络卷开发时 target-dir 重定向到本地盘) /.cargo/config.toml diff --git a/devs/docs/audits/vendor-delta-ce8bffab.md b/devs/docs/audits/vendor-delta-ce8bffab.md new file mode 100644 index 00000000..28c3cdb1 --- /dev/null +++ b/devs/docs/audits/vendor-delta-ce8bffab.md @@ -0,0 +1,2380 @@ +# Vendor calc-delta — a82a33b4 → ce8bffab + +| | old | new | +|---|---|---| +| commit | `a82a33b4fbdb7306ed93159664f7abe2c6819413` | `ce8bffaba31f8e68cfce70579e1c96465e7c133c` | +| PoB2 version | 0.21.0 | 0.22.0 | + +Generated by `pipeline/diff-vendor-calcs.sh a82a33b4fbdb7306ed93159664f7abe2c6819413 ce8bffaba31f8e68cfce70579e1c96465e7c133c`. +This is a Phase-1 engine work list: the calc-file hunks and overlay entry +deltas below are what a hand-port must chase before flipping the parity golden. + +## Calc module diff + +Files PoBR mirrors: `Modules/Calc*.lua` + `Modules/ModParser.lua`. +Full hunks are in the collapsed blocks; a file whose diff exceeds +`MAX_HUNK_LINES=800` shows only its stat line. + +| file | +added | -removed | +|---|---:|---:| +| `Modules/CalcActiveSkill.lua` | 53 | 6 | +| `Modules/CalcDefence.lua` | 75 | 17 | +| `Modules/CalcOffence.lua` | 62 | 40 | +| `Modules/CalcPerform.lua` | 193 | 118 | +| `Modules/Calcs.lua` | 236 | 125 | +| `Modules/CalcSections.lua` | 9 | 3 | +| `Modules/CalcSetup.lua` | 115 | 24 | +| `Modules/CalcTools.lua` | 12 | 4 | +| `Modules/CalcTriggers.lua` | 3 | 3 | +| `Modules/ModParser.lua` | 62 | 34 | + +
Modules/CalcActiveSkill.lua (115 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcActiveSkill.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcActiveSkill.lua +index 90af8a6..49d8ca6 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcActiveSkill.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcActiveSkill.lua +@@ -88,7 +88,7 @@ function calcs.mergeSkillInstanceMods(env, modList, skillEffect, statSet, extraS + local grantedEffect = skillEffect.grantedEffect + local selectedGlobalStats = { } + local function mergeStatSet(set, onlyGlobals) +- local stats = calcLib.buildSkillInstanceStats(skillEffect, grantedEffect, set) ++ local stats = calcLib.buildSkillInstanceStats(skillEffect, grantedEffect, set, env.useAltGemQualityStats) + if extraStats and extraStats[1] then + for _, stat in pairs(extraStats) do + stats[stat.key] = (stats[stat.key] or 0) + stat.value +@@ -230,6 +230,35 @@ function calcs.createActiveSkill(activeEffect, supportList, env, actor, socketGr + return activeSkill + end + ++local function getSourceGemPropertyInfo(env, activeSkill) ++ local activeEffect = activeSkill.activeEffect ++ local sourceGem = activeEffect.srcInstance ++ if not sourceGem or not activeEffect.gemData or activeEffect.gemData.tags.support then ++ return { } ++ end ++ ++ env.sourceGemPropertyInfo = env.sourceGemPropertyInfo or { } ++ if not env.sourceGemPropertyInfo[sourceGem] then ++ local modList = new("ModList", activeSkill.actor.modDB) ++ local supportCount = 0 ++ for _, supportEffect in ipairs(activeSkill.supportList) do ++ if supportEffect.isSupporting and supportEffect.isSupporting[sourceGem] then ++ calcs.mergeSkillInstanceMods(env, modList, supportEffect) ++ if not supportEffect.grantedEffect.hidden then ++ supportCount = supportCount + 1 ++ end ++ end ++ end ++ modList:NewMod("Multiplier:SupportCount", "BASE", supportCount, "Support Count") ++ env.sourceGemPropertyInfo[sourceGem] = modList:Tabulate("LIST", { ++ skillName = activeEffect.gemData.name, ++ skillGem = activeEffect.gemData, ++ slotName = activeSkill.slotName, ++ }, "SupportedGemProperty") ++ end ++ return env.sourceGemPropertyInfo[sourceGem] ++end ++ + function calcs.getActiveSkillDisplayName(activeSkill) + local skillName = activeSkill.activeEffect.grantedEffect.name + local skillMinion = activeSkill.minion +@@ -525,7 +554,7 @@ function calcs.buildActiveSkillModList(env, activeSkill) + end + + -- Apply stat-map flagged skill flags. +- for stat, statValue in pairs(calcLib.buildSkillInstanceStats(activeEffect, activeGrantedEffect, activeStatSet)) do ++ for stat, statValue in pairs(calcLib.buildSkillInstanceStats(activeEffect, activeGrantedEffect, activeStatSet, env.useAltGemQualityStats)) do + local map = activeGrantedEffect.statMap[stat] + if statValue ~= 0 and map and map.skillFlag then + skillFlags[map.skillFlag] = true +@@ -639,7 +668,7 @@ function calcs.buildActiveSkillModList(env, activeSkill) + end + + -- Calculate distance from enemy +- effectiveRange = env.configInput.enemyDistance ++ effectiveRange = env.configInput.enemyDistance or env.configPlaceholder.enemyDistance + + -- Build config structure for modifier searches + activeSkill.skillCfg = { +@@ -740,12 +769,15 @@ function calcs.buildActiveSkillModList(env, activeSkill) + skillModList:NewMod("GemLevel", "BASE", activeSkill.activeEffect.srcInstance and activeSkill.activeEffect.srcInstance.level or activeSkill.activeEffect.level, "Max Level") + if activeSkill.activeEffect.srcInstance and activeSkill.activeEffect.srcInstance.corrupted and not (activeSkill.activeEffect.srcInstance.fromItem or activeSkill.activeEffect.srcInstance.fromTree or activeSkill.activeEffect.grantedEffect.fromItem or activeSkill.activeEffect.grantedEffect.fromTree) then + skillModList:NewMod("GemCorruptionLevel", "BASE", activeSkill.activeEffect.srcInstance.corruptLevel, "Corruption") ++ activeSkill.skillCfg.skillCond["GemCorrupted"] = true + end +- for _, supportProperty in ipairs(skillModList:Tabulate("LIST", activeSkill.skillCfg, "SupportedGemProperty")) do ++ for _, supportProperty in ipairs(getSourceGemPropertyInfo(env, activeSkill)) do + local value = supportProperty.value +- if value.keyword == "grants_active_skill" and activeSkill.activeEffect.gemData and not activeSkill.activeEffect.gemData.tags.support then ++ if value.keyword == "grants_active_skill" then + activeEffect[value.key] = activeEffect[value.key] + value.value +- skillModList:NewMod("GemSupport".. value.key:gsub("^%l", string.upper), "BASE", value.value, supportProperty.mod.source, #supportProperty.mod > 0 and supportProperty.mod[1] or nil) ++ local gemTag = supportProperty.mod[1] ++ gemTag = gemTag and gemTag.type == "GemTag" and gemTag or nil ++ skillModList:NewMod("GemSupport".. value.key:gsub("^%l", string.upper), "BASE", value.value, supportProperty.mod.source, gemTag) + end + end + +@@ -763,6 +795,12 @@ function calcs.buildActiveSkillModList(env, activeSkill) + grantedEffectLevel[k] = v + end + end ++ if activeEffect.srcInstance and activeEffect.srcInstance.noReservation then ++ for _, resource in ipairs({ "mana", "life", "spirit" }) do ++ grantedEffectLevel[resource.."ReservationFlat"] = 0 ++ grantedEffectLevel[resource.."ReservationPercent"] = 0 ++ end ++ end + activeEffect.grantedEffectLevel = grantedEffectLevel + + -- Add extra modifiers from granted effect level +@@ -996,6 +1034,15 @@ function calcs.buildActiveSkillModList(env, activeSkill) + if tag.type == "GlobalEffect" then + effectType = tag.effectType + effectName = tag.effectName or activeGrantedEffect.name ++ if activeSkill.minion and activeSkill.minion.minionData then ++ if effectName:find("{0}", 1, true) then ++ effectName = effectName:gsub("{0}", activeSkill.minion.minionData.name) ++ elseif activeGrantedEffect.minionList and effectName:match("^Companion") then ++ effectName = "Companion: "..activeSkill.minion.minionData.name ++ elseif activeGrantedEffect.minionList and effectName:match("^Spectre") then ++ effectName = "Spectre: "..activeSkill.minion.minionData.name ++ end ++ end + effectTag = tag + break + end +``` + +
+ +
Modules/CalcDefence.lua (170 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcDefence.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcDefence.lua +index a96b386..e155293 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcDefence.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcDefence.lua +@@ -225,6 +225,16 @@ function calcs.doActorLifeManaSpiritReservation(actor) + pool.Life.baseFlat = skillModList:Sum("BASE", skillCfg, "LifeCostBase") + (activeSkill.activeEffect.grantedEffectLevel.cost.Life or 0) + end + pool.Life.basePercent = activeSkill.skillData.lifeReservationPercent or activeSkill.activeEffect.grantedEffectLevel.lifeReservationPercent or 0 ++ if activeSkill.skillTypes[SkillType.IsBlasphemy] and activeSkill.activeEffect.srcInstance.supportEffect and activeSkill.activeEffect.srcInstance.supportEffect.isSupporting then ++ -- Sadly no better way to get key/val table element count in lua. ++ local instances = 0 ++ for _ in pairs(activeSkill.activeEffect.srcInstance.supportEffect.isSupporting) do ++ instances = instances + 1 ++ end ++ for name, values in pairs(pool) do ++ values.baseFlat = values.baseFlat + (activeSkill.skillData["blasphemyReservationFlat" .. name] or 0) * instances ++ end ++ end + if skillModList:Flag(skillCfg, "BloodMagicReserved") then + pool.Life.baseFlat = pool.Life.baseFlat + pool.Mana.baseFlat + pool.Mana.baseFlat = 0 +@@ -235,6 +245,13 @@ function calcs.doActorLifeManaSpiritReservation(actor) + activeSkill.skillData["LifeReservationPercentForced"] = activeSkill.skillData["ManaReservationPercentForced"] + activeSkill.skillData["ManaReservationPercentForced"] = nil + end ++ local spiritToLifeReservation = skillModList:Sum("BASE", skillCfg, "LifeReservePercentPerSpirit") ++ if spiritToLifeReservation > 0 then ++ pool.Life.basePercent = pool.Life.basePercent + pool.Spirit.baseFlat * spiritToLifeReservation ++ pool.Spirit.baseFlat = 0 ++ pool.Life.basePercent = pool.Life.basePercent + pool.Spirit.basePercent * spiritToLifeReservation ++ pool.Spirit.basePercent = 0 ++ end + for name, values in pairs(pool) do + values.more = skillModList:More(skillCfg, name.."Reserved", "Reserved") + values.inc = skillModList:Sum("INC", skillCfg, name.."Reserved", "Reserved") +@@ -269,18 +286,6 @@ function calcs.doActorLifeManaSpiritReservation(actor) + values.count = activeSkillCount + local minionFreeSpiritCount = skillModList:Sum("BASE", skillCfg, "MinionFreeSpiritCount") + values.reservedFlat = values.reservedFlat * m_max(activeSkillCount - minionFreeSpiritCount, 0) +- end +- if activeSkill.skillTypes[SkillType.IsBlasphemy] and activeSkill.activeEffect.srcInstance.supportEffect and activeSkill.activeEffect.srcInstance.supportEffect.isSupporting and activeSkill.skillData["blasphemyReservationFlat" .. name] then +- -- Sadly no better way to get key/val table element count in lua. +- local instances = 0 +- for _ in pairs(activeSkill.activeEffect.srcInstance.supportEffect.isSupporting) do +- instances = instances + 1 +- end +- +- -- Extra reservation of blasphemy needs to be separated from the reservation caused by curses +- local blasphemyFlat = activeSkill.skillData["blasphemyReservationFlat" .. name] +- local blasphemyEffectiveFlat = m_max(round(blasphemyFlat * mult * (100 + values.inc) / 100 * values.more / (1 + values.efficiency / 100) / values.efficiencyMore, 0), 0) +- values.reservedFlat = values.reservedFlat + blasphemyEffectiveFlat * instances + end + -- Blood Sacrament increases reservation per stage channelled + if activeSkill.skillCfg.skillName == "Blood Sacrament" and activeSkill.activeStageCount then +@@ -485,6 +490,9 @@ function calcs.reducePoolsByDamage(poolTable, damageTable, actor) + if output.TotalRadianceSentinelLife then + alliesTakenBeforeYou["radianceSentinel"] = { remaining = output.TotalRadianceSentinelLife, percent = output.RadianceSentinelAllyDamageMitigation / 100 } + end ++ if output.TotalCompanionLife then ++ alliesTakenBeforeYou["companion"] = { remaining = output.TotalCompanionLife, percent = output.CompanionAllyDamageMitigation / 100 } ++ end + if output.AlliedEnergyShield then + alliesTakenBeforeYou["soulLink"] = { remaining = output.AlliedEnergyShield, percent = output.SoulLinkMitigation / 100 } + end +@@ -702,6 +710,9 @@ local function incomingDamageBreakdown(breakdownTable, poolsRemaining, output) + if output.TotalRadianceSentinelLife and output.TotalRadianceSentinelLife > 0 then + t_insert(breakdownTable, s_format("\t%d "..colorCodes.GEM.."Total Sentinel of Radiance Life ^7(%d remaining)", output.TotalRadianceSentinelLife - poolsRemaining.AlliesTakenBeforeYou["radianceSentinel"].remaining, poolsRemaining.AlliesTakenBeforeYou["radianceSentinel"].remaining)) + end ++ if output.TotalCompanionLife and output.TotalCompanionLife > 0 then ++ t_insert(breakdownTable, s_format("\t%d "..colorCodes.GEM.."Total Companion Life ^7(%d remaining)", output.TotalCompanionLife - poolsRemaining.AlliesTakenBeforeYou["companion"].remaining, poolsRemaining.AlliesTakenBeforeYou["companion"].remaining)) ++ end + if output.AlliedEnergyShield and output.AlliedEnergyShield > 0 then + t_insert(breakdownTable, s_format("\t%d "..colorCodes.GEM.."Total Allied Energy shield ^7(%d remaining)", output.AlliedEnergyShield - poolsRemaining.AlliesTakenBeforeYou["soulLink"].remaining, poolsRemaining.AlliesTakenBeforeYou["soulLink"].remaining)) + end +@@ -1369,8 +1380,8 @@ function calcs.defence(env, actor) + local gainRate = modDB:Sum("BASE", nil, source.name .. "GainAs" .. target.name) + local rate = source.conversionRate[target.name] + gainRate + if rate > 0 then +- local targetBase = math.ceil(globalBase * rate / 100) +- local targetTotalBase = math.ceil(totalBase * rate / 100) ++ local targetBase = round(globalBase * rate / 100) ++ local targetTotalBase = round(totalBase * rate / 100) + target.globalBase = target.globalBase + targetBase + target.totalBase = target.totalBase + targetTotalBase + if breakdown then +@@ -2913,13 +2924,23 @@ function calcs.buildDefenceEstimations(env, actor) + end + + -- from spectres +- output["SpectreAllyDamageMitigation"] = modDB:Sum("BASE", nil, "takenFromSpectresBeforeYou") ++ output["SpectreAllyDamageMitigation"] = modDB:Sum("BASE", nil, "TakenFromSpectresBeforeYou") + if output["SpectreAllyDamageMitigation"] ~= 0 then +- output["TotalSpectreLife"] = modDB:Sum("BASE", nil, "TotalSpectreLife") ++ output["TotalSpectreLife"] = modDB:Override(nil, "TotalSpectreLife") or modDB:Sum("BASE", nil, "TotalSpectreLife") ++ if breakdown then ++ breakdown["TotalSpectreLife"] = { } ++ if modDB:Override(nil, "TotalSpectreLife") then ++ t_insert(breakdown["TotalSpectreLife"], s_format("%d ^8(from config)", output["TotalSpectreLife"])) ++ else ++ for _, spectre in ipairs(actor.spectreLifeList or { }) do ++ t_insert(breakdown["TotalSpectreLife"], s_format("%d ^8(%s)", spectre.life, spectre.name)) ++ end ++ end ++ end + end + + -- from totems +- output["TotemAllyDamageMitigation"] = modDB:Sum("BASE", nil, "takenFromTotemsBeforeYou") ++ output["TotemAllyDamageMitigation"] = modDB:Sum("BASE", nil, "TakenFromTotemsBeforeYou") + if output["TotemAllyDamageMitigation"] ~= 0 then + output["TotalTotemLife"] = modDB:Sum("BASE", nil, "TotalTotemLife") + end +@@ -2936,6 +2957,31 @@ function calcs.buildDefenceEstimations(env, actor) + output["TotalRadianceSentinelLife"] = modDB:Sum("BASE", nil, "TotalRadianceSentinelLife") + end + ++ -- from companions ++ local companionMitigation = modDB:Sum("BASE", nil, "TakenFromCompanionBeforeYou") ++ local companionMitigationFromDeflected = modDB:Sum("BASE", nil, "TakenFromCompanionBeforeYouFromDeflected") ++ output["CompanionAllyDamageMitigation"] = companionMitigation + companionMitigationFromDeflected * (output.DeflectChance or 0) / 100 ++ if output["CompanionAllyDamageMitigation"] ~= 0 then ++ output["TotalCompanionLife"] = modDB:Override(nil, "TotalCompanionLife") or modDB:Sum("BASE", nil, "TotalCompanionLife") ++ if breakdown then ++ breakdown["TotalCompanionLife"] = { } ++ if modDB:Override(nil, "TotalCompanionLife") then ++ t_insert(breakdown["TotalCompanionLife"], s_format("%d ^8(from config)", output["TotalCompanionLife"])) ++ else ++ for _, companion in ipairs(actor.companionLifeList or { }) do ++ t_insert(breakdown["TotalCompanionLife"], s_format("%d ^8(%s)", companion.life, companion.name)) ++ end ++ end ++ if companionMitigationFromDeflected ~= 0 then ++ breakdown["CompanionAllyDamageMitigation"] = { ++ s_format("%d%% ^8(taken from hits)", companionMitigation), ++ s_format("+ %d%% x %.2f ^8(taken from deflected hits, scaled by deflect chance)", companionMitigationFromDeflected, (output.DeflectChance or 0) / 100), ++ s_format("= %.1f%%", output["CompanionAllyDamageMitigation"]), ++ } ++ end ++ end ++ end ++ + -- from Allied Energy Shield + output["SoulLinkMitigation"] = modDB:Sum("BASE", nil, "TakenFromParentESBeforeYou") + if output["SoulLinkMitigation"] ~= 0 then +@@ -3038,6 +3084,9 @@ function calcs.buildDefenceEstimations(env, actor) + if output.TotalRadianceSentinelLife then + alliesTakenBeforeYou["radianceSentinel"] = { remaining = output.TotalRadianceSentinelLife, percent = output.RadianceSentinelAllyDamageMitigation / 100 } + end ++ if output.TotalCompanionLife then ++ alliesTakenBeforeYou["companion"] = { remaining = output.TotalCompanionLife, percent = output.CompanionAllyDamageMitigation / 100 } ++ end + if output.AlliedEnergyShield then + alliesTakenBeforeYou["soulLink"] = { remaining = output.AlliedEnergyShield, percent = output.SoulLinkMitigation / 100 } + end +@@ -3603,6 +3652,15 @@ function calcs.buildDefenceEstimations(env, actor) + local poolProtected = output["TotalRadianceSentinelLife"] / (output["RadianceSentinelAllyDamageMitigation"] / 100) * (1 - output["RadianceSentinelAllyDamageMitigation"] / 100) + output[damageType.."TotalHitPool"] = m_max(output[damageType.."TotalHitPool"] - poolProtected, 0) + m_min(output[damageType.."TotalHitPool"], poolProtected) / (1 - output["RadianceSentinelAllyDamageMitigation"] / 100) + end ++ -- companions ++ if output["TotalCompanionLife"] and output["TotalCompanionLife"] > 0 then ++ if output["CompanionAllyDamageMitigation"] >= 100 then ++ output[damageType.."TotalHitPool"] = output[damageType.."TotalHitPool"] + output["TotalCompanionLife"] ++ else ++ local poolProtected = output["TotalCompanionLife"] / (output["CompanionAllyDamageMitigation"] / 100) * (1 - output["CompanionAllyDamageMitigation"] / 100) ++ output[damageType.."TotalHitPool"] = m_max(output[damageType.."TotalHitPool"] - poolProtected, 0) + m_min(output[damageType.."TotalHitPool"], poolProtected) / (1 - output["CompanionAllyDamageMitigation"] / 100) ++ end ++ end + -- soul link + if output["AlliedEnergyShield"] and output["AlliedEnergyShield"] > 0 then + local poolProtected = output["AlliedEnergyShield"] / (output["SoulLinkMitigation"] / 100) * (1 - output["SoulLinkMitigation"] / 100) +``` + +
+ +
Modules/CalcOffence.lua (293 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcOffence.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcOffence.lua +index a750c61..b6de2bf 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcOffence.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcOffence.lua +@@ -1131,6 +1131,11 @@ function calcs.offence(env, actor, activeSkill) + modDB:NewMod("DPS", "MORE", detonateTwice, "Grenade Activate Twice") + end + ++ -- Dual wield DPS multiplier ++ if skillFlags.bothWeaponAttack and skillData.doubleHitsWhenDualWielding then ++ skillModList:NewMod("DPS", "MORE", 100, "Hits with both weapons") ++ end ++ + if skillModList:Flag(nil, "HasSeals") and not skillModList:Flag(nil, "NoRepeatBonuses") then + -- Applies seal bonuses based on seal count + local totalCastSpeed = 1 / activeSkill.activeEffect.grantedEffect.castTime * calcLib.mod(skillModList, skillCfg, "Speed") +@@ -2386,9 +2391,6 @@ function calcs.offence(env, actor, activeSkill) + -- Unarmed override for Concoction skills + if skillFlags.unarmed then + source = copyTable(data.unarmedWeaponData[env.classId]) +- if skillData.CritChance then +- source.CritChance = skillData.CritChance +- end + end + if source.FacebreakerItemDamage and activeSkill.activeEffect.grantedEffect.weaponTypes and activeSkill.activeEffect.grantedEffect.weaponTypes["One Hand Mace"] then + for _, damageType in ipairs(dmgTypeList) do +@@ -2399,6 +2401,9 @@ function calcs.offence(env, actor, activeSkill) + if critOverride and source.type and source.type ~= "None" then + source.CritChance = critOverride + end ++ if skillData.CritChance then ++ source.CritChance = skillData.CritChance ++ end + t_insert(passList, { + label = "Main Hand", + source = source, +@@ -2416,9 +2421,6 @@ function calcs.offence(env, actor, activeSkill) + -- Unarmed override for Concoction skills + if skillFlags.unarmed then + source = copyTable(data.unarmedWeaponData[env.classId]) +- if skillData.CritChance then +- source.CritChance = skillData.CritChance +- end + end + if critOverride and source.type and source.type ~= "None" then + source.CritChance = critOverride +@@ -2468,7 +2470,7 @@ function calcs.offence(env, actor, activeSkill) + elseif mode == "AVERAGE" then + output[stat] = ((output.MainHand[stat] or 0) + (output.OffHand[stat] or 0)) / 2 + elseif mode == "CRIT" then +- if skillFlags.bothWeaponAttack and skillData.doubleHitsWhenDualWielding then ++ if skillFlags.bothWeaponAttack and skillData.combinesHitsWhenDualWielding then + output[stat] = (output.MainHand[stat] or 0) + (output.OffHand[stat] or 0) - ((output.MainHand[stat] or 0) * (output.OffHand[stat] or 0) / 100) + else + output[stat] = ((output.MainHand[stat] or 0) + (output.OffHand[stat] or 0)) / 2 +@@ -2542,7 +2544,7 @@ function calcs.offence(env, actor, activeSkill) + end + elseif mode == "DPS" then + output[stat] = (output.MainHand[stat] or 0) + (output.OffHand[stat] or 0) +- if not skillData.doubleHitsWhenDualWielding then ++ if not skillData.combinesHitsWhenDualWielding then + output[stat] = output[stat] / 2 + end + end +@@ -2741,7 +2743,7 @@ function calcs.offence(env, actor, activeSkill) + baseTime = 1 / ( source.AttackRate or 1 ) + skillModList:Sum("BASE", cfg, "Speed") + end + else +- baseTime = skillData.castTimeOverride or activeSkill.activeEffect.grantedEffect.castTime or 1 ++ baseTime = (skillData.castTimeOverride or activeSkill.activeEffect.grantedEffect.castTime or 1) + skillModList:Sum("BASE", cfg, "Speed") + end + local more = skillModList:More(cfg, "Speed") + output.Repeats = globalOutput.Repeats or 1 +@@ -3057,11 +3059,26 @@ function calcs.offence(env, actor, activeSkill) + end + elseif skillFlags.bothWeaponAttack then + if breakdown then +- breakdown.Speed = { +- "Both weapons:", +- s_format("2 / (1 / %.2f + 1 / %.2f)", output.MainHand.Speed, output.OffHand.Speed), +- s_format("= %.2f", output.Speed), +- } ++ if skillData.combinesHitsWhenDualWielding then ++ breakdown.Speed = { ++ "Combined hit from both weapons:", ++ s_format("1 / (1 / %.2f + 1 / %.2f)", output.MainHand.Speed, output.OffHand.Speed), ++ s_format("= %.2f", output.Speed), ++ } ++ elseif skillData.doubleHitsWhenDualWielding then ++ breakdown.Speed = { ++ "Simultaneous hits from each weapon:", ++ s_format("2 / (1 / %.2f + 1 / %.2f)", output.MainHand.Speed, output.OffHand.Speed), ++ s_format("%.2f ^8(hits twice per attack)", output.Speed), ++ s_format("= %.2f", output.Speed), ++ } ++ else ++ breakdown.Speed = { ++ "Alternating both weapons:", ++ s_format("2 / (1 / %.2f + 1 / %.2f)", output.MainHand.Speed, output.OffHand.Speed), ++ s_format("= %.2f", output.Speed), ++ } ++ end + end + end + if skillData.channelTimeMultiplier then +@@ -3185,7 +3202,7 @@ function calcs.offence(env, actor, activeSkill) + + if env.mode_buffs then + -- Iterative over all the active skills to account for exerted attacks provided by warcries +- if not activeSkill.skillTypes[SkillType.NeverExertable] and not activeSkill.skillTypes[SkillType.Triggered] and not activeSkill.skillTypes[SkillType.Channel] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] and not activeSkill.skillTypes[SkillType.Retaliation] then ++ if not activeSkill.skillTypes[SkillType.NeverExertable] and not activeSkill.skillTypes[SkillType.Triggered] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] and not activeSkill.skillTypes[SkillType.Retaliation] then + for index, value in ipairs(actor.activeSkillList) do + if value.activeEffect.grantedEffect.name == "Ancestral Cry" and activeSkill.skillTypes[SkillType.MeleeSingleTarget] and not globalOutput.AncestralCryCalculated then + globalOutput.AncestralCryDuration = calcSkillDuration(value.skillModList, value.skillCfg, value.skillData, env, enemyDB) +@@ -3875,9 +3892,11 @@ function calcs.offence(env, actor, activeSkill) + output.DoubleDamageEffect = output.DoubleDamageChance / 100 + output.ScaledDamageEffect = output.ScaledDamageEffect * (1 + output.DoubleDamageEffect + output.TripleDamageEffect) + +- skillData.dpsMultiplier = ( skillData.dpsMultiplier or 1 ) * calcLib.mod(skillModList, skillCfg, "DPS") + +- local hitRate = output.HitChance / 100 * (globalOutput.HitSpeed or globalOutput.Speed) * skillData.dpsMultiplier ++ ++ output.DpsMultiplier = ( skillData.dpsMultiplier or 1 ) * calcLib.mod(skillModList, skillCfg, "DPS") ++ ++ local hitRate = output.HitChance / 100 * (globalOutput.HitSpeed or globalOutput.Speed) * output.DpsMultiplier + + local enemyRarity = (enemyDB:Flag(nil, "Condition:Unique") and "Unique" or (enemyDB:Flag(nil, "Condition:RareOrUnique") and "Rare" or "Normal")) + -- Calculate culling DPS +@@ -4425,7 +4444,7 @@ function calcs.offence(env, actor, activeSkill) + local repeatPenalty = skillModList:Flag(nil, "HasSeals") and skillModList:Flag(nil, "DamageSeal") and not skillModList:Flag(nil, "NoRepeatBonuses") and calcLib.mod(skillModList, skillCfg, "SealRepeatPenalty") or 1 + globalOutput.AverageBurstDamage = output.AverageDamage + output.AverageDamage * (globalOutput.AverageBurstHits - 1) * repeatPenalty or 0 + globalOutput.ShowBurst = globalOutput.AverageBurstHits > 1 +- output.TotalDPS = output.AverageDamage * (globalOutput.HitSpeed or globalOutput.Speed) * skillData.dpsMultiplier * quantityMultiplier ++ output.TotalDPS = output.AverageDamage * (globalOutput.HitSpeed or globalOutput.Speed) * output.DpsMultiplier * quantityMultiplier + if breakdown then + if output.CritEffect ~= 1 then + breakdown.AverageHit = { } +@@ -4517,7 +4536,7 @@ function calcs.offence(env, actor, activeSkill) + local portionElemental = (output.AverageHit / PvpTvalue / PvpElemental2 ) ^ PvpElemental1 * PvpTvalue * PvpElemental2 * percentageElemental + output.PvpAverageHit = (portionNonElemental + portionElemental) * PvpMultiplier + output.PvpAverageDamage = output.PvpAverageHit * output.HitChance / 100 +- output.PvpTotalDPS = output.PvpAverageDamage * (globalOutput.HitSpeed or globalOutput.Speed) * skillData.dpsMultiplier ++ output.PvpTotalDPS = output.PvpAverageDamage * (globalOutput.HitSpeed or globalOutput.Speed) * output.DpsMultiplier + + -- fix for these being nan + if output.PvpAverageHit ~= output.PvpAverageHit then +@@ -4579,6 +4598,7 @@ function calcs.offence(env, actor, activeSkill) + combineStat("CritBifurcates", "AVERAGE") + combineStat("AverageDamage", "DPS") + combineStat("PvpAverageDamage", "DPS") ++ combineStat("DpsMultiplier", "DPS") + combineStat("TotalDPS", "DPS") + combineStat("PvpTotalDPS", "DPS") + combineStat("LifeLeechDuration", "DPS") +@@ -4648,8 +4668,10 @@ function calcs.offence(env, actor, activeSkill) + if breakdown then + breakdown.AverageDamage = { } + t_insert(breakdown.AverageDamage, "Both weapons:") +- if skillData.doubleHitsWhenDualWielding then ++ if skillData.combinesHitsWhenDualWielding then + t_insert(breakdown.AverageDamage, s_format("%.1f + %.1f ^8(skill hits with both weapons at once)", output.MainHand.AverageDamage, output.OffHand.AverageDamage)) ++ elseif skillData.doubleHitsWhenDualWielding then ++ t_insert(breakdown.AverageDamage, s_format("(%.1f + %.1f) / 2 ^8(skill hits once with each weapon)", output.MainHand.AverageDamage, output.OffHand.AverageDamage)) + else + t_insert(breakdown.AverageDamage, s_format("(%.1f + %.1f) / 2 ^8(skill alternates weapons)", output.MainHand.AverageDamage, output.OffHand.AverageDamage)) + end +@@ -4657,7 +4679,7 @@ function calcs.offence(env, actor, activeSkill) + if skillFlags.isPvP then + breakdown.PvpAverageDamage = { } + t_insert(breakdown.PvpAverageDamage, "Both weapons:") +- if skillData.doubleHitsWhenDualWielding then ++ if skillData.combinesHitsWhenDualWielding then + t_insert(breakdown.PvpAverageDamage, s_format("%.1f + %.1f ^8(skill hits with both weapons at once)", output.MainHand.PvpAverageDamage, output.OffHand.PvpAverageDamage)) + else + t_insert(breakdown.PvpAverageDamage, s_format("(%.1f + %.1f) / 2 ^8(skill alternates weapons)", output.MainHand.PvpAverageDamage, output.OffHand.PvpAverageDamage)) +@@ -4691,8 +4713,8 @@ function calcs.offence(env, actor, activeSkill) + output.HitSpeed and s_format("x %.2f ^8(hit rate)", output.HitSpeed) or s_format("x %.2f ^8(cast rate)", output.Speed), + } + end +- if skillData.dpsMultiplier ~= 1 then +- t_insert(breakdown.TotalDPS, s_format("x %g ^8(DPS multiplier for this skill)", skillData.dpsMultiplier)) ++ if output.DpsMultiplier ~= 1 then ++ t_insert(breakdown.TotalDPS, s_format("x %g ^8(DPS multiplier for this skill)", output.DpsMultiplier)) + end + if quantityMultiplier > 1 then + t_insert(breakdown.TotalDPS, s_format("x %g ^8(quantity multiplier for this skill)", quantityMultiplier)) +@@ -4709,8 +4731,8 @@ function calcs.offence(env, actor, activeSkill) + s_format("%.1f ^8(average pvp hit)", output.PvpAverageDamage), + output.HitSpeed and s_format("x %.2f ^8(hit rate)", output.HitSpeed) or s_format("x %.2f ^8(%s rate)", output.Speed, rateType), + } +- if skillData.dpsMultiplier ~= 1 then +- t_insert(breakdown.PvpTotalDPS, s_format("x %g ^8(DPS multiplier for this skill)", skillData.dpsMultiplier)) ++ if output.DpsMultiplier ~= 1 then ++ t_insert(breakdown.PvpTotalDPS, s_format("x %g ^8(DPS multiplier for this skill)", output.DpsMultiplier)) + end + if quantityMultiplier > 1 then + t_insert(breakdown.PvpTotalDPS, s_format("x %g ^8(quantity multiplier for this skill)", quantityMultiplier)) +@@ -4720,7 +4742,7 @@ function calcs.offence(env, actor, activeSkill) + end + + if skillFlags.minion then +- skillData.summonSpeed = output.SummonedMinionsPerCast * (output.HitSpeed or output.Speed) * skillData.dpsMultiplier ++ skillData.summonSpeed = output.SummonedMinionsPerCast * (output.HitSpeed or output.Speed) * output.DpsMultiplier + end + + -- Calculate leech rates +@@ -4766,7 +4788,7 @@ function calcs.offence(env, actor, activeSkill) + output.ManaLeechGainRate = output.ManaLeechRate + output.ManaOnHitRate + end + if breakdown then +- local hitRate = output.HitChance / 100 * (globalOutput.HitSpeed or globalOutput.Speed) * skillData.dpsMultiplier ++ local hitRate = output.HitChance / 100 * (globalOutput.HitSpeed or globalOutput.Speed) * output.DpsMultiplier + if skillFlags.leechLife then + breakdown.LifeLeech = breakdown.leech(output.LifeLeechInstant, output.LifeLeechInstantRate, output.LifeLeechInstances, output.Life, "LifeLeechRate", output.MaxLifeLeechRate, output.LifeLeechDuration, output.LifeLeechInstantProportion, hitRate) + end +@@ -5064,7 +5086,7 @@ function calcs.offence(env, actor, activeSkill) + local ailmentChance = output[ailment .. "ChanceOnHit"] / 100 * (1 - output.CritChance / 100) + output[ailment .. "ChanceOnCrit"] / 100 * output.CritChance / 100 + + -- The average number of ailment that will be active on the enemy at once +- local ailmentStacks = output.HitChance / 100 * ailmentChance * skillData.dpsMultiplier ++ local ailmentStacks = output.HitChance / 100 * ailmentChance * output.DpsMultiplier + local configStacks = enemyDB:Sum("BASE", nil, "Multiplier:" .. ailment .. "Stacks") + if not skillData.triggeredOnDeath then + if output.Cooldown then +@@ -5106,8 +5128,8 @@ function calcs.offence(env, actor, activeSkill) + elseif (globalOutput.HitSpeed or globalOutput.Speed) > 0 then + t_insert(globalBreakdown[ailment .. "StackPotential"], s_format("* (%.2f / %.2f) ^8(Duration / Attack Time)", globalOutput[ailment .. "Duration"], (globalOutput.HitTime or output.Time))) + end +- if skillData.dpsMultiplier ~= 1 then +- t_insert(globalBreakdown[ailment .. "StackPotential"], s_format("* %g ^8(DPS multiplier for this skill)", skillData.dpsMultiplier)) ++ if output.DpsMultiplier ~= 1 then ++ t_insert(globalBreakdown[ailment .. "StackPotential"], s_format("* %g ^8(DPS multiplier for this skill)", output.DpsMultiplier)) + end + end + t_insert(globalBreakdown[ailment .. "StackPotential"], s_format("/ %d ^8(max number of stacks)", maxStacks)) +@@ -5949,7 +5971,7 @@ function calcs.offence(env, actor, activeSkill) + elseif band(dotCfg.keywordFlags, KeywordFlag.Trap) ~= 0 then + speed = output.TrapThrowingSpeed + end +- output.TotalDot = m_min(output.TotalDotInstance * speed * output.Duration * skillData.dpsMultiplier * quantityMultiplier, data.misc.DotDpsCap) ++ output.TotalDot = m_min(output.TotalDotInstance * speed * output.Duration * output.DpsMultiplier * quantityMultiplier, data.misc.DotDpsCap) + output.TotalDotCalcSection = output.TotalDot + if breakdown then + breakdown.TotalDot = { +@@ -5957,8 +5979,8 @@ function calcs.offence(env, actor, activeSkill) + s_format("x %.2f ^8(hits per second)", speed), + s_format("x %.2f ^8(skill duration)", output.Duration), + } +- if skillData.dpsMultiplier ~= 1 then +- t_insert(breakdown.TotalDot, s_format("x %g ^8(DPS multiplier for this skill)", skillData.dpsMultiplier)) ++ if output.DpsMultiplier ~= 1 then ++ t_insert(breakdown.TotalDot, s_format("x %g ^8(DPS multiplier for this skill)", output.DpsMultiplier)) + end + if quantityMultiplier > 1 then + t_insert(breakdown.TotalDot, s_format("x %g ^8(quantity multiplier for this skill)", quantityMultiplier)) +@@ -6206,13 +6228,13 @@ function calcs.offence(env, actor, activeSkill) + end + if skillFlags.impale then + local mainHandImpaleDPS, offHandImpaleDPS +- if skillFlags.attack and skillData.doubleHitsWhenDualWielding and skillFlags.bothWeaponAttack then ++ if skillFlags.attack and skillData.combinesHitsWhenDualWielding and skillFlags.bothWeaponAttack then + -- separately combine +- mainHandImpaleDPS = output.MainHand.impaleStoredHitAvg * ((output.MainHand.ImpaleModifier or 1) - 1) * output.MainHand.HitChance / 100 * skillData.dpsMultiplier +- offHandImpaleDPS = output.OffHand.impaleStoredHitAvg * ((output.OffHand.ImpaleModifier or 1) - 1) * output.OffHand.HitChance / 100 * skillData.dpsMultiplier ++ mainHandImpaleDPS = output.MainHand.impaleStoredHitAvg * ((output.MainHand.ImpaleModifier or 1) - 1) * output.MainHand.HitChance / 100 * output.DpsMultiplier ++ offHandImpaleDPS = output.OffHand.impaleStoredHitAvg * ((output.OffHand.ImpaleModifier or 1) - 1) * output.OffHand.HitChance / 100 * output.DpsMultiplier + output.ImpaleDPS = mainHandImpaleDPS + offHandImpaleDPS + else +- output.ImpaleDPS = output.PhysicalStoredCombinedAvg * ((output.ImpaleModifier or 1) - 1) * output.HitChance / 100 * skillData.dpsMultiplier ++ output.ImpaleDPS = output.PhysicalStoredCombinedAvg * ((output.ImpaleModifier or 1) - 1) * output.HitChance / 100 * output.DpsMultiplier + end + if skillData.showAverage then + output.WithImpaleDPS = output.AverageDamage + output.ImpaleDPS +@@ -6228,7 +6250,7 @@ function calcs.offence(env, actor, activeSkill) + output.CombinedDPS = output.CombinedDPS + output.ImpaleDPS + if breakdown then + breakdown.ImpaleDPS = {} +- if skillFlags.attack and skillData.doubleHitsWhenDualWielding and skillFlags.bothWeaponAttack then ++ if skillFlags.attack and skillData.combinesHitsWhenDualWielding and skillFlags.bothWeaponAttack then + t_insert(breakdown.ImpaleDPS, s_format("Main Hand:")) + t_insert(breakdown.ImpaleDPS, s_format("%.2f ^8(MH average physical hit before mitigation)", output.MainHand.impaleStoredHitAvg)) + t_insert(breakdown.ImpaleDPS, s_format("x %.2f ^8(MH chance to hit)", output.MainHand.HitChance / 100)) +@@ -6249,8 +6271,8 @@ function calcs.offence(env, actor, activeSkill) + if skillFlags.notAverage then + t_insert(breakdown.ImpaleDPS, output.HitSpeed and s_format("x %.2f ^8(hit rate)", output.HitSpeed) or s_format("x %.2f ^8(%s rate)", output.Speed, skillFlags.attack and "attack" or "cast")) + end +- if skillData.dpsMultiplier ~= 1 then +- t_insert(breakdown.ImpaleDPS, s_format("x %g ^8(dps multiplier for this skill)", skillData.dpsMultiplier)) ++ if output.DpsMultiplier ~= 1 then ++ t_insert(breakdown.ImpaleDPS, s_format("x %g ^8(dps multiplier for this skill)", output.DpsMultiplier)) + end + if quantityMultiplier > 1 then + t_insert(breakdown.ImpaleDPS, s_format("x %g ^8(quantity multiplier for this skill)", quantityMultiplier)) +``` + +
+ +
Modules/CalcPerform.lua (441 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcPerform.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcPerform.lua +index c903d6b..dbc4bb9 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcPerform.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcPerform.lua +@@ -86,7 +86,7 @@ local legacies = { + }, + LegacyOfGold = { + effects = { +- { stat = "ItemRarity", type = "INC", value = 45 }, ++ { stat = "LootRarity", type = "INC", value = 45 }, + } + }, + LegacyOfGranite = { +@@ -345,10 +345,6 @@ local function doActorAttribsConditions(env, actor) + end + end + +- if env.configInput.conditionShapeshifted then +- condList["Shapeshifted"] = true +- end +- + if actor.mainSkill.skillTypes[SkillType.Bear] then + condList["Shapeshifted"] = true + condList["BearForm"] = true +@@ -779,7 +775,7 @@ local function doActorMisc(env, actor) + condList["LeechingEnergyShield"] = true + end + if modDB:Flag(nil, "Condition:CanGainRage") or modDB:Sum("BASE", nil, "RageRegen") > 0 then +- local maxStacks = modDB:Sum("BASE", skillCfg, "MaximumRage") ++ local maxStacks = modDB:Sum("BASE", skillCfg, "MaximumRage") * calcLib.mod(modDB, nil, "MaximumRage") + local minStacks = m_min(modDB:Sum("BASE", nil, "MinimumRage"), maxStacks) + local rageConfig = modDB:Sum("BASE", nil, "Multiplier:RageStack") + local stacks = m_max(m_min(rageConfig, maxStacks), (minStacks > 0 and minStacks) or 0) +@@ -1034,6 +1030,125 @@ function calcs.actionSpeedMod(actor) + return actionSpeedMod + end + ++-- Initialises a minion's modifier database with its base stats (life, defences, resists), ++-- monster type mods, tamed beast mods and player-granted mods, for the given owning skill ++local function initMinionModDB(env, activeSkill) ++ local skillFlags ++ if env.mode == "CALCS" then ++ skillFlags = activeSkill.activeEffect.statSetCalcs.skillFlags ++ else ++ skillFlags = activeSkill.activeEffect.statSet.skillFlags ++ end ++ local modDB = env.modDB ++ local minion = activeSkill.minion ++ minion.modDB.multipliers["Level"] = minion.level ++ calcs.initModDB(env, minion.modDB) ++ local baseLife = minion.lifeTable[minion.level] * minion.minionData.life ++ if minion.hostile then ++ baseLife = baseLife * (env.data.mapLevelLifeMult[env.enemyLevel] or 1) ++ end ++ minion.modDB:NewMod("Life", "BASE", m_floor(baseLife), "Base") ++ if minion.minionData.energyShield then ++ minion.modDB:NewMod("LifeConvertToEnergyShield", "BASE", minion.minionData.energyShield * 100, "Base") ++ end ++ --Armour formula is math.floor((10 + 2 * level) * 1.067 ^ level) ++ minion.modDB:NewMod("Armour", "BASE", round(env.data.monsterArmourTable[minion.level] * (minion.minionData.armour or 1)), "Base") ++ --Evasion formula is math.floor((50 + 16 * level + 16 * level * (MonsterType.Evasion / 100)) * (1.0212 ^ level) ++ minion.modDB:NewMod("Evasion", "BASE", round(env.data.monsterEvasionTable[minion.level] * (minion.minionData.evasion or 1)), "Base") ++ if modDB:Flag(nil, "MinionAccuracyEqualsAccuracy") then ++ minion.modDB:NewMod("Accuracy", "BASE", calcLib.val(modDB, "Accuracy") + calcLib.val(modDB, "Dex") * (modDB:Override(nil, "DexAccBonusOverride") or data.misc.AccuracyPerDexBase), "Player") ++ else ++ -- Minions no longer need Accuracy as of patch 0.3.0 ++ minion.modDB:NewMod("CannotBeEvaded", "FLAG", 1, "Minion Attacks always hit") ++ end ++ minion.modDB:NewMod("CritMultiplier", "BASE", env.data.monsterConstants["base_critical_hit_damage_bonus"] + env.data.playerMinionIntrinsicStats["base_critical_hit_damage_bonus"], "Base") ++ if skillFlags.summonBeast then ++ minion.modDB:NewMod("FireResist", "BASE", minion.minionData.companionFireResist, "Base") ++ minion.modDB:NewMod("ColdResist", "BASE", minion.minionData.companionColdResist, "Base") ++ minion.modDB:NewMod("LightningResist", "BASE", minion.minionData.companionLightningResist, "Base") ++ minion.modDB:NewMod("ChaosResist", "BASE", minion.minionData.companionChaosResist, "Base") ++ else ++ minion.modDB:NewMod("FireResist", "BASE", minion.minionData.fireResist, "Base") ++ minion.modDB:NewMod("ColdResist", "BASE", minion.minionData.coldResist, "Base") ++ minion.modDB:NewMod("LightningResist", "BASE", minion.minionData.lightningResist, "Base") ++ minion.modDB:NewMod("ChaosResist", "BASE", minion.minionData.chaosResist, "Base") ++ end ++ minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base") ++ minion.modDB:NewMod("PhysicalHeavyStunBuildup", "MORE", data.monsterConstants["physical_hit_damage_stun_multiplier_+%_final_from_ot"], "Physical Damage") ++ minion.modDB:NewMod("EnemyHeavyStunBuildup", "MORE", data.monsterConstants["melee_hit_damage_stun_multiplier_+%_final_from_ot"], "Melee Damage", ModFlag.Melee) ++ minion.modDB:NewMod("Damage", "MORE", minion.hiddenDamageFixup * 100, "Hidden Level Scaling") ++ for _, mod in ipairs(minion.minionData.modList) do ++ minion.modDB:AddMod(mod) ++ end ++ for _, mod in ipairs(activeSkill.extraSkillModList) do ++ minion.modDB:AddMod(mod) ++ end ++ if env.talismanModList then ++ -- Adding mods provided by "Necromantic Talisman" ++ minion.modDB:AddList(env.talismanModList) ++ end ++ if env.theIronMass and minion.type == "RaisedSkeleton" then ++ minion.modDB:AddList(env.theIronMass) ++ end ++ if activeSkill.skillData.minionUseBowAndQuiver then ++ if env.player.weaponData1.type == "Bow" then ++ minion.modDB:AddList(env.player.itemList["Weapon 1"].slotModList[1]) ++ end ++ if env.player.itemList["Weapon 2"] and env.player.itemList["Weapon 2"].type == "Quiver" then ++ local quiverEffectMod = env.player.modDB:Sum("INC", nil, "EffectOfBonusesFromQuiver") / 100 ++ if quiverEffectMod > 0 then ++ for _, mod in ipairs(env.player.itemList["Weapon 2"].modList) do ++ local modCopy = copyTable(mod) ++ modCopy.source = "Many Sources:" .. tostring(quiverEffectMod * 100) .. "% Quiver Bonus Effect" ++ minion.modDB:ScaleAddMod(modCopy, quiverEffectMod) ++ end ++ end ++ end ++ end ++ if minion.itemSet or minion.uses then ++ for slotName, slot in pairs(env.build.itemsTab.slots) do ++ if minion.uses[slotName] then ++ local item ++ if minion.itemSet then ++ if slot.weaponSet == 1 and minion.itemSet.useSecondWeaponSet then ++ slotName = slotName .. " Swap" ++ end ++ item = env.build.itemsTab.items[minion.itemSet[slotName].selItemId] ++ else ++ item = env.player.itemList[slotName] ++ end ++ if item then ++ minion.itemList[slotName] = item ++ minion.modDB:AddList(item.modList or item.slotModList[slot.slotNum]) ++ end ++ end ++ end ++ end ++ if modDB:Flag(nil, "StrengthAddedToMinions") then ++ minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Player") ++ end ++ if modDB:Flag(nil, "StrengthAddedToCompanions") and activeSkill.skillTypes[SkillType.Companion] then ++ minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Sturdy Ally") ++ end ++ if modDB:Flag(nil, "HalfStrengthAddedToMinions") then ++ minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str") * 0.5), "Player") ++ end ++ if modDB:Flag(nil, "DexterityAddedToMinions") then ++ minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Dead can Dance") ++ end ++ if modDB:Flag(nil, "DexterityAddedToCompanions") and activeSkill.skillTypes[SkillType.Companion] then ++ minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Tandem Assault") ++ end ++end ++ ++local function addMinionModifiers(modList, skillCfg, minion) ++ for _, value in ipairs(modList:List(skillCfg, "MinionModifier")) do ++ if not value.type or minion.type == value.type then ++ minion.modDB:AddMod(value.mod) ++ end ++ end ++end ++ + -- Finalises the environment and performs the stat calculations: + -- 1. Merges keystone modifiers + -- 2. Initialises minion skills +@@ -1075,97 +1190,7 @@ function calcs.perform(env, skipEHP) + -- Initialise minion modifier database + output.Minion = { } + env.minion.output = output.Minion +- env.minion.modDB.multipliers["Level"] = env.minion.level +- calcs.initModDB(env, env.minion.modDB) +- local baseLife = env.minion.lifeTable[env.minion.level] * env.minion.minionData.life +- if env.minion.hostile then +- baseLife = baseLife * (env.data.mapLevelLifeMult[env.enemyLevel] or 1) +- end +- env.minion.modDB:NewMod("Life", "BASE", m_floor(baseLife), "Base") +- if env.minion.minionData.energyShield then +- env.minion.modDB:NewMod("LifeConvertToEnergyShield", "BASE", env.minion.minionData.energyShield * 100, "Base") +- end +- --Armour formula is math.floor((10 + 2 * level) * 1.067 ^ level) +- env.minion.modDB:NewMod("Armour", "BASE", round(env.data.monsterArmourTable[env.minion.level] * (env.minion.minionData.armour or 1)), "Base") +- --Evasion formula is math.floor((50 + 16 * level + 16 * level * (MonsterType.Evasion / 100)) * (1.0212 ^ level) +- env.minion.modDB:NewMod("Evasion", "BASE", round(env.data.monsterEvasionTable[env.minion.level] * (env.minion.minionData.evasion or 1)), "Base") +- if modDB:Flag(nil, "MinionAccuracyEqualsAccuracy") then +- env.minion.modDB:NewMod("Accuracy", "BASE", calcLib.val(modDB, "Accuracy") + calcLib.val(modDB, "Dex") * (modDB:Override(nil, "DexAccBonusOverride") or data.misc.AccuracyPerDexBase), "Player") +- else +- -- Minions no longer need Accuracy as of patch 0.3.0 +- env.minion.modDB:NewMod("CannotBeEvaded", "FLAG", 1, "Minion Attacks always hit") +- end +- env.minion.modDB:NewMod("CritMultiplier", "BASE", env.data.monsterConstants["base_critical_hit_damage_bonus"] + env.data.playerMinionIntrinsicStats["base_critical_hit_damage_bonus"], "Base") +- env.minion.modDB:NewMod("FireResist", "BASE", env.minion.minionData.fireResist, "Base") +- env.minion.modDB:NewMod("ColdResist", "BASE", env.minion.minionData.coldResist, "Base") +- env.minion.modDB:NewMod("LightningResist", "BASE", env.minion.minionData.lightningResist, "Base") +- env.minion.modDB:NewMod("ChaosResist", "BASE", env.minion.minionData.chaosResist, "Base") +- env.minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base") +- env.minion.modDB:NewMod("PhysicalHeavyStunBuildup", "MORE", data.monsterConstants["physical_hit_damage_stun_multiplier_+%_final_from_ot"], "Physical Damage") +- env.minion.modDB:NewMod("EnemyHeavyStunBuildup", "MORE", data.monsterConstants["melee_hit_damage_stun_multiplier_+%_final_from_ot"], "Melee Damage", ModFlag.Melee) +- env.minion.modDB:NewMod("Damage", "MORE", env.minion.hiddenDamageFixup * 100, "Hidden Level Scaling") +- for _, mod in ipairs(env.minion.minionData.modList) do +- env.minion.modDB:AddMod(mod) +- end +- for _, mod in ipairs(env.player.mainSkill.extraSkillModList) do +- env.minion.modDB:AddMod(mod) +- end +- if env.talismanModList then +- -- Adding mods provided by "Necromantic Talisman" +- env.minion.modDB:AddList(env.talismanModList) +- end +- if env.theIronMass and env.minion.type == "RaisedSkeleton" then +- env.minion.modDB:AddList(env.theIronMass) +- end +- if env.player.mainSkill.skillData.minionUseBowAndQuiver then +- if env.player.weaponData1.type == "Bow" then +- env.minion.modDB:AddList(env.player.itemList["Weapon 1"].slotModList[1]) +- end +- if env.player.itemList["Weapon 2"] and env.player.itemList["Weapon 2"].type == "Quiver" then +- local quiverEffectMod = env.player.modDB:Sum("INC", nil, "EffectOfBonusesFromQuiver") / 100 +- if quiverEffectMod > 0 then +- for _, mod in ipairs(env.player.itemList["Weapon 2"].modList) do +- local modCopy = copyTable(mod) +- modCopy.source = "Many Sources:" .. tostring(quiverEffectMod * 100) .. "% Quiver Bonus Effect" +- env.minion.modDB:ScaleAddMod(modCopy, quiverEffectMod) +- end +- end +- end +- end +- if env.minion.itemSet or env.minion.uses then +- for slotName, slot in pairs(env.build.itemsTab.slots) do +- if env.minion.uses[slotName] then +- local item +- if env.minion.itemSet then +- if slot.weaponSet == 1 and env.minion.itemSet.useSecondWeaponSet then +- slotName = slotName .. " Swap" +- end +- item = env.build.itemsTab.items[env.minion.itemSet[slotName].selItemId] +- else +- item = env.player.itemList[slotName] +- end +- if item then +- env.minion.itemList[slotName] = item +- env.minion.modDB:AddList(item.modList or item.slotModList[slot.slotNum]) +- end +- end +- end +- end +- if modDB:Flag(nil, "StrengthAddedToMinions") then +- env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Player") +- end +- if modDB:Flag(nil, "StrengthAddedToCompanions") and env.player.mainSkill.skillTypes[SkillType.Companion] then +- env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Sturdy Ally") +- end +- if modDB:Flag(nil, "HalfStrengthAddedToMinions") then +- env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str") * 0.5), "Player") +- end +- if modDB:Flag(nil, "DexterityAddedToMinions") then +- env.minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Dead can Dance") +- end +- if modDB:Flag(nil, "DexterityAddedToCompanions") and env.player.mainSkill.skillTypes[SkillType.Companion] then +- env.minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Tandem Assault") +- end ++ initMinionModDB(env, env.player.mainSkill) + end + if env.talismanModList then + -- Accounting for "Necromantic Talisman" +@@ -1415,7 +1440,7 @@ function calcs.perform(env, skipEHP) + + for slot, item in pairs(env.player.itemList) do + local slotEffectMod = modDB:Sum("INC", nil, "EffectOfBonusesFrom" .. slot) / 100 +- if slotEffectMod > 0 then ++ if slotEffectMod > 0 and slot ~= "Amulet" then + if item.name:match("Kalandra's Touch") then + if slot == "Ring 2" then + item = env.player.itemList["Ring 1"] +@@ -1774,11 +1799,7 @@ function calcs.perform(env, skipEHP) + end + end + else +- for _, value in ipairs(env.player.mainSkill.skillModList:List(env.player.mainSkill.skillCfg, "MinionModifier")) do +- if not value.type or env.minion.type == value.type then +- env.minion.modDB:AddMod(value.mod) +- end +- end ++ addMinionModifiers(env.player.mainSkill.skillModList, env.player.mainSkill.skillCfg, env.minion) + for _, name in ipairs(env.minion.modDB:List(nil, "Keystone")) do + if env.spec.tree.keystoneMap[name] then + env.minion.modDB:AddList(env.spec.tree.keystoneMap[name].modList) +@@ -2414,23 +2435,23 @@ function calcs.perform(env, skipEHP) + end + if buff.type == "Curse" then + curse.modList = new("ModList") +- curse.modList:ScaleAddList(buff.modList, mult) ++ curse.modList:ScaleAddList(buff.modList, mult, true) + if partyTabEnableExportBuffs then + buffExports["Curse"][buff.name] = { isMark = curse.isMark, effectMult = curse.isMark and mult or (1 + inc / 100) * moreMark, modList = buff.modList } + end + else + -- Curse applies a buff; scale by curse effect, then buff effect + local temp = new("ModList") +- temp:ScaleAddList(buff.modList, mult) ++ temp:ScaleAddList(buff.modList, mult, true) + curse.buffModList = new("ModList") + local buffInc = modDB:Sum("INC", skillCfg, "BuffEffectOnSelf") + local buffMore = modDB:More(skillCfg, "BuffEffectOnSelf") +- curse.buffModList:ScaleAddList(temp, (1 + buffInc / 100) * buffMore) ++ curse.buffModList:ScaleAddList(temp, (1 + buffInc / 100) * buffMore, true) + if env.minion then + curse.minionBuffModList = new("ModList") + local buffInc = env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf") + local buffMore = env.minion.modDB:More(nil, "BuffEffectOnSelf") +- curse.minionBuffModList:ScaleAddList(temp, (1 + buffInc / 100) * buffMore) ++ curse.minionBuffModList:ScaleAddList(temp, (1 + buffInc / 100) * buffMore, true) + end + end + t_insert(curses, curse) +@@ -2894,7 +2915,7 @@ function calcs.perform(env, skipEHP) + local cfg = { skillName = grantedEffect.name } + local inc = modDB:Sum("INC", cfg, "CurseEffectOnSelf") + gemModList:Sum("INC", nil, "CurseEffectAgainstPlayer") + local more = modDB:More(cfg, "CurseEffectOnSelf") * gemModList:More(nil, "CurseEffectAgainstPlayer") +- modDB:ScaleAddList(curseModList, m_max((1 + inc / 100) * more, 0)) ++ modDB:ScaleAddList(curseModList, m_max((1 + inc / 100) * more, 0), true) + end + elseif not enemyDB:Flag(nil, "Hexproof") or modDB:Flag(nil, "CursesIgnoreHexproof") then + local curse = { +@@ -2903,7 +2924,7 @@ function calcs.perform(env, skipEHP) + priority = determineCursePriority(grantedEffect.name), + } + curse.modList = new("ModList") +- curse.modList:ScaleAddList(curseModList, (1 + enemyDB:Sum("INC", nil, "CurseEffectOnSelf") / 100) * enemyDB:More(nil, "CurseEffectOnSelf")) ++ curse.modList:ScaleAddList(curseModList, (1 + enemyDB:Sum("INC", nil, "CurseEffectOnSelf") / 100) * enemyDB:More(nil, "CurseEffectOnSelf"), true) + t_insert(dest, curse) + end + end +@@ -2928,7 +2949,7 @@ function calcs.perform(env, skipEHP) + else + mult = mult * enemyDB:More(nil, "CurseEffectOnSelf") + end +- newCurse.modList:ScaleAddList(curse.modList, mult) ++ newCurse.modList:ScaleAddList(curse.modList, mult, true) + t_insert(allyCurses, newCurse) + end + +@@ -3057,11 +3078,7 @@ function calcs.perform(env, skipEHP) + modDB.multipliers["BuffOnSelf"] = (modDB.multipliers["BuffOnSelf"] or 0) + 1 + end + if env.minion then +- for _, value in ipairs(modList:List(env.player.mainSkill.skillCfg, "MinionModifier")) do +- if not value.type or env.minion.type == value.type then +- env.minion.modDB:AddMod(value.mod) +- end +- end ++ addMinionModifiers(modList, env.player.mainSkill.skillCfg, env.minion) + end + end + if env.minion then +@@ -3072,7 +3089,13 @@ function calcs.perform(env, skipEHP) + for _, modList in pairs(debuffs) do + enemyDB:AddList(modList) + end +- modDB.multipliers["CurseOnEnemy"] = #curseSlots ++ local cursesInCurseSlots = {} ++ for _, slot in ipairs(curseSlots) do ++ if not slot.isMark then ++ table.insert(cursesInCurseSlots, slot) ++ end ++ end ++ modDB.multipliers["CurseOnEnemy"] = #cursesInCurseSlots + for _, slot in ipairs(curseSlots) do + enemyDB.conditions["Cursed"] = true + if slot.isMark then +@@ -3224,6 +3247,9 @@ function calcs.perform(env, skipEHP) + }, + } + ++ -- precalculate life for rathpith Spells have 3% increased Magnitude of Ailments per 100 maximum ++ -- Life cultivated mod ++ calcs.doActorLifeManaSpirit(env.player, true) + local hitFlag + if env.mode == "CALCS" then + hitFlag = env.player.mainSkill.activeEffect.statSetCalcs.skillFlags.hit +@@ -3248,9 +3274,9 @@ function calcs.perform(env, skipEHP) + -- if not, use the generic modifiers + -- Scorch/Sap/Brittle do not have guaranteed sources from hits, and therefore will only end up in this bit of code if it's not supposed to apply the skillModList, which is bad + if ailment ~= "Scorch" and ailment ~= "Sap" and ailment ~= "Brittle" and not env.player.mainSkill.skillModList:Flag(nil, "Cannot"..ailment) and hitFlag and modDB:Flag(nil, "ChecksHighestDamage") then +- effect = effect * calcLib.mod(env.player.mainSkill.skillModList, env.player.mainSkill.skillModList.skillCfg, "Enemy"..ailment.."Magnitude", "AilmentMagnitude") * calcLib.mod(enemyDB, nil, "Self"..ailment.."Magnitude", "AilmentMagnitude") ++ effect = effect * calcLib.mod(env.player.mainSkill.skillModList, env.player.mainSkill.skillCfg, "Enemy" .. ailment .. "Magnitude", "AilmentMagnitude") * calcLib.mod(enemyDB, nil, "Self" .. ailment .. "Magnitude", "AilmentMagnitude") + else +- effect = effect * calcLib.mod(env.player.mainSkill.skillModList, env.player.mainSkill.skillModList.skillCfg, "Enemy"..ailment.."Magnitude", "AilmentMagnitude") * calcLib.mod(enemyDB, nil, "Self"..ailment.."Magnitude", "AilmentMagnitude") ++ effect = effect * calcLib.mod(env.player.mainSkill.skillModList, env.player.mainSkill.skillCfg, "Enemy" .. ailment .. "Magnitude", "AilmentMagnitude") * calcLib.mod(enemyDB, nil, "Self" .. ailment .. "Magnitude", "AilmentMagnitude") + end + modDB:NewMod(ailment.."Override", "BASE", effect, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + if mod.name == ailment.."Minimum" then +@@ -3294,6 +3320,55 @@ function calcs.perform(env, skipEHP) + enemyDB:NewMod("DamageTaken", "INC", enemyDB:Sum("INC", nil, "DamageTakenConsecratedGround") * effect, "Consecrated Ground") + end + ++ local function calcMinionLifePool(env, buffs, includeSkill) ++ local totalLife = 0 ++ local lifeList = { } ++ local seenMinions = { } ++ for _, activeSkill in ipairs(env.player.activeSkillList) do ++ local skillFlags = env.mode == "CALCS" and activeSkill.activeEffect.statSetCalcs.skillFlags or activeSkill.activeEffect.statSet.skillFlags ++ local minion = activeSkill.minion ++ if minion and not seenMinions[minion] and not skillFlags.disable and includeSkill(activeSkill, skillFlags) then ++ seenMinions[minion] = true ++ if minion ~= env.minion then ++ minion.output = minion.output or { } ++ initMinionModDB(env, activeSkill) ++ addMinionModifiers(activeSkill.skillModList, activeSkill.skillCfg, minion) ++ for _, name in ipairs(minion.modDB:List(nil, "Keystone")) do ++ if env.spec.tree.keystoneMap[name] then ++ minion.modDB:AddList(env.spec.tree.keystoneMap[name].modList) ++ end ++ end ++ for _, modList in pairs(buffs) do ++ addMinionModifiers(modList, activeSkill.skillCfg, minion) ++ end ++ doActorAttribsConditions(env, minion) ++ end ++ calcs.doActorLifeManaSpirit(minion, true) ++ totalLife = totalLife + minion.output.Life ++ t_insert(lifeList, { name = minion.minionData and minion.minionData.name or activeSkill.activeEffect.grantedEffect.name, life = minion.output.Life }) ++ end ++ end ++ return totalLife, lifeList ++ end ++ ++ -- Total life of spectres, for "% of Damage from Hits is taken from your Spectres' Life before you" ++ if modDB:HasMod("BASE", nil, "TakenFromSpectresBeforeYou") and not modDB:Override(nil, "TotalSpectreLife") then ++ local totalSpectreLife, spectreLifeList = calcMinionLifePool(env, buffs, function(activeSkill, skillFlags) ++ return skillFlags.spectre and not activeSkill.skillTypes[SkillType.MinionsAreUndamagable] ++ end) ++ modDB:NewMod("TotalSpectreLife", "BASE", totalSpectreLife, "Spectres") ++ env.player.spectreLifeList = spectreLifeList ++ end ++ ++ -- Total life of damageable companions, for "% of Damage from Hits is taken from your Companion's Life before you" ++ if modDB:HasMod("BASE", nil, "TakenFromCompanionBeforeYou", "TakenFromCompanionBeforeYouFromDeflected") and not modDB:Override(nil, "TotalCompanionLife") then ++ local totalCompanionLife, companionLifeList = calcMinionLifePool(env, buffs, function(activeSkill) ++ return activeSkill.skillTypes[SkillType.Companion] and not activeSkill.skillTypes[SkillType.MinionsAreUndamagable] ++ end) ++ modDB:NewMod("TotalCompanionLife", "BASE", totalCompanionLife, "Companions") ++ env.player.companionLifeList = companionLifeList ++ end ++ + -- Defence/offence calculations + calcs.defence(env, env.player) + local function getSkillExposureEffect(source, element) +``` + +
+ +
Modules/Calcs.lua (450 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/Calcs.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/Calcs.lua +index 12f8169..f8cf2e8 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/Calcs.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/Calcs.lua +@@ -124,14 +124,42 @@ function calcs.getMiscCalculator(build) + -- Run base calculation pass + local env, cachedPlayerDB, cachedEnemyDB, cachedMinionDB = calcs.initEnv(build, "CALCULATOR") + calcs.perform(env) +- local fullDPS = calcs.calcFullDPS(build, "CALCULATOR", {}, { cachedPlayerDB = cachedPlayerDB, cachedEnemyDB = cachedEnemyDB, cachedMinionDB = cachedMinionDB, env = nil}) ++ -- Capture per-skill Full DPS results and their input references during the base pass, ++ -- so accelerated calls can reuse results for skills whose inputs are unchanged ++ local fullDPSStore = { } ++ local fullDPS = calcs.calcFullDPS(build, "CALCULATOR", {}, { cachedPlayerDB = cachedPlayerDB, cachedEnemyDB = cachedEnemyDB, cachedMinionDB = cachedMinionDB, env = nil, fullDPSCache = { store = fullDPSStore, capture = true }}) + local usedFullDPS = #fullDPS.skills > 0 + if usedFullDPS then + env.player.output.SkillDPS = fullDPS.skills + env.player.output.FullDPS = fullDPS.combinedDPS + env.player.output.FullDotDPS = fullDPS.TotalDotDPS + end +- return function(override, useFullDPS) ++ local fastEnv ++ return function(override, useFullDPS, fastCalcOptions) ++ if fastCalcOptions then ++ if fastCalcOptions.fullDPSOnly and usedFullDPS and useFullDPS then ++ -- The caller only reads the FullDPS roll-up (e.g. sorting gems by Full DPS), and ++ -- calcFullDPS builds its own environments, so the main-skill pass can be skipped entirely. ++ -- The base-pass cache store lets skills with unchanged inputs reuse their captured results ++ local fullDPS = calcs.calcFullDPS(build, "CALCULATOR", override, { cachedPlayerDB = cachedPlayerDB, cachedEnemyDB = cachedEnemyDB, cachedMinionDB = cachedMinionDB, env = nil, fullDPSCache = { store = fullDPSStore } }) ++ return { SkillDPS = fullDPS.skills, FullDPS = fullDPS.combinedDPS, FullDotDPS = fullDPS.TotalDotDPS } ++ end ++ -- Accelerated pass for hot loops (e.g. gem dropdown DPS sorting): reuse the cached ++ -- DBs and environment so unchanged state (tree, items, requirements - per the ++ -- accelerate flags) is carried over instead of being rebuilt for every call. ++ -- The first call builds the reusable environment from scratch, like calcFullDPS does. ++ local accelerate = fastEnv and fastCalcOptions or nil ++ fastEnv = calcs.initEnv(build, "CALCULATOR", override, { cachedPlayerDB = cachedPlayerDB, cachedEnemyDB = cachedEnemyDB, cachedMinionDB = cachedMinionDB, env = fastEnv, accelerate = accelerate }) ++ fastEnv.override = override ++ calcs.perform(fastEnv, fastCalcOptions.skipEHP) ++ if (useFullDPS ~= false or build.viewMode == "TREE") and usedFullDPS then ++ local fullDPS = calcs.calcFullDPS(build, "CALCULATOR", override, { cachedPlayerDB = cachedPlayerDB, cachedEnemyDB = cachedEnemyDB, cachedMinionDB = cachedMinionDB, env = nil}) ++ fastEnv.player.output.SkillDPS = fullDPS.skills ++ fastEnv.player.output.FullDPS = fullDPS.combinedDPS ++ fastEnv.player.output.FullDotDPS = fullDPS.TotalDotDPS ++ end ++ return fastEnv.player.output ++ end + local env, cachedPlayerDB, cachedEnemyDB, cachedMinionDB = calcs.initEnv(build, "CALCULATOR", override) + -- we need to preserve the override somewhere for use by possible trigger-based build-outs with overrides + env.override = override +@@ -149,9 +177,100 @@ function calcs.getMiscCalculator(build) + end, env.player.output + end + ++-- Output fields harvested from each Full DPS calc pass; captured into plain snapshot ++-- tables so that cached passes can be merged identically to freshly computed ones ++local harvestFields = { "TotalDPS", "BleedDPS", "CorruptingBloodDPS", "IgniteDPS", "BurningGroundDPS", "PoisonDPS", "CausticGroundDPS", "ImpaleDPS", "DecayDPS", "TotalDot", "CullMultiplier" } ++local function captureFields(output) ++ local captured = { } ++ for _, field in ipairs(harvestFields) do ++ captured[field] = output[field] ++ end ++ return captured ++end ++ ++local mergeStatsSpec = { ++ { key = "BleedDPS", target = "bleedDPS", mode = "max" }, ++ { key = "CorruptingBloodDPS", target = "corruptingBloodDPS", mode = "max" }, ++ { key = "IgniteDPS", target = "igniteDPS", mode = "max" }, ++ { key = "BurningGroundDPS", target = "burningGroundDPS", mode = "max" }, ++ { key = "PoisonDPS", target = "poisonDPS", mode = "max" }, ++ { key = "CausticGroundDPS", target = "causticGroundDPS", mode = "max" }, ++ { key = "ImpaleDPS", target = "impaleDPS", mode = "add", scaled = true }, ++ { key = "DecayDPS", target = "decayDPS", mode = "add" }, ++ { key = "CullMultiplier", target = "cullingMulti", mode = "cull" }, ++} ++ ++-- Tolerant modifier equality for the Full DPS input diff: mod tables are pointer-stable ++-- across initEnv calls within one build revision, except for a few mods constructed per ++-- pass (e.g. GemLevel, level-scaled support mods), which are compared structurally instead. ++local function modsEqual(a, b) ++ return a == b or (type(a) == "table" and type(b) == "table" and tableDeepEquals(a, b) and tableDeepEquals(b, a)) ++end ++local function modListsEqual(refList, curList) ++ if #refList ~= #curList then ++ return false ++ end ++ for i = 1, #refList do ++ if not modsEqual(refList[i], curList[i]) then ++ return false ++ end ++ end ++ return true ++end ++ ++-- Capture the coupling surface of an environment: the state through which one skill's gems ++-- can influence other skills' results - buffs/auras/curses each skill provides (buffList) ++-- and exposure it can inflict. While this surface is unchanged, a skill whose own mod list ++-- is unchanged must produce unchanged results. ++local exposureElements = { "Fire", "Cold", "Lightning", "Chaos" } ++local function captureCouplingSurface(env) ++ local surface = { mods = { }, meta = { } } ++ for _, skill in ipairs(env.player.activeSkillList) do ++ for _, buff in ipairs(skill.buffList or { }) do ++ surface.meta[#surface.meta + 1] = tostring(buff.type) .. "/" .. tostring(buff.name) ++ for _, mod in ipairs(buff.modList or { }) do ++ surface.mods[#surface.mods + 1] = mod ++ end ++ end ++ local modList = skill.baseSkillModList ++ if modList then ++ if modList:HasMod("FLAG", nil, "InflictExposure") then ++ surface.meta[#surface.meta + 1] = "expoFlag" ++ end ++ for _, element in ipairs(exposureElements) do ++ if modList:HasMod("BASE", nil, element .. "ExposureChance") then ++ surface.meta[#surface.meta + 1] = "expo" .. element ++ end ++ end ++ end ++ end ++ surface.metaStr = table.concat(surface.meta, ";") ++ return surface ++end ++local function surfacesEqual(refSurface, curSurface) ++ return refSurface.metaStr == curSurface.metaStr and modListsEqual(refSurface.mods, curSurface.mods) ++end ++ + function calcs.calcFullDPS(build, mode, override, specEnv) + local fullEnv, cachedPlayerDB, cachedEnemyDB, cachedMinionDB = calcs.initEnv(build, mode, override, specEnv) + local usedEnv = nil ++ -- Optional per-skill result cache driven by input diffing (specEnv.fullDPSCache): ++ -- with capture set, each skill's harvested results are stored in the cache store along ++ -- with its input references (own mod list + the env's coupling surface); on later calls, ++ -- a skill whose references are unchanged merges its cached results instead of recalculating ++ local fullDPSCache = specEnv and specEnv.fullDPSCache ++ local cacheStore = fullDPSCache and fullDPSCache.store ++ local surfaceSame = false ++ if cacheStore then ++ local curSurface = captureCouplingSurface(fullEnv) ++ if fullDPSCache.capture then ++ cacheStore.snapshots = { } ++ cacheStore.refs = { } ++ cacheStore.surface = curSurface ++ else ++ surfaceSame = cacheStore.surface ~= nil and surfacesEqual(cacheStore.surface, curSurface) ++ end ++ end + + local fullDPS = { + combinedDPS = 0, +@@ -169,52 +288,97 @@ function calcs.calcFullDPS(build, mode, override, specEnv) + cullingMulti = 0 + } + +- local poisonSource = "" +- local bleedSource = "" +- local corruptingBloodSource = "" +- local igniteSource = "" +- local burningGroundSource = "" +- local causticGroundSource = "" ++ ++ local sources = { } ++ ++ local function mergeStats(out, count, sourceName) ++ for _, stat in ipairs(mergeStatsSpec) do ++ local value = out[stat.key] ++ if value then ++ if stat.mode == "max" then ++ if value > fullDPS[stat.target] then ++ fullDPS[stat.target] = value ++ sources[stat.target] = sourceName ++ end ++ elseif stat.mode == "add" then ++ if value > 0 then ++ fullDPS[stat.target] = fullDPS[stat.target] + value * (stat.scaled and count or 1) ++ end ++ elseif stat.mode == "cull" then ++ if value > 1 and value > fullDPS[stat.target] then ++ fullDPS[stat.target] = value ++ end ++ end ++ end ++ end ++ end ++ ++ -- Merge one captured calc pass into the Full DPS totals ++ local function mergePass(pass) ++ for _, actor in ipairs(pass.actors) do ++ local out = actor.out ++ if out.TotalDPS and out.TotalDPS > 0 then ++ t_insert(fullDPS.skills, { name = actor.name, dps = out.TotalDPS, count = actor.count, trigger = actor.trigger, skillPart = actor.skillPart }) ++ fullDPS.combinedDPS = fullDPS.combinedDPS + out.TotalDPS * actor.count ++ end ++ mergeStats(out, actor.count, actor.sourceName) ++ if out.TotalDot and out.TotalDot > 0 and actor.dotScale then ++ fullDPS.dotDPS = fullDPS.dotDPS + out.TotalDot * actor.dotScale ++ end ++ end ++ end + + for _, activeSkill in ipairs(fullEnv.player.activeSkillList) do + if activeSkill.socketGroup and activeSkill.socketGroup.includeInFullDPS then +- local activeSkillCount, enabled = calcs.getActiveSkillCount(activeSkill) +- if enabled then ++ local uuid = cacheStore and cacheSkillUUID(activeSkill, fullEnv) ++ local canCacheSkill = not (activeSkill.triggeredBy or activeSkill.skillData.triggered) ++ local cachedPasses ++ if canCacheSkill and surfaceSame and activeSkill.baseSkillModList then ++ local ref = cacheStore.refs[uuid] ++ if ref and cacheStore.snapshots[uuid] and modListsEqual(ref, activeSkill.baseSkillModList) then ++ cachedPasses = cacheStore.snapshots[uuid] ++ end ++ end ++ local activeSkillCount, enabled ++ if not cachedPasses then ++ activeSkillCount, enabled = calcs.getActiveSkillCount(activeSkill) ++ end ++ if cachedPasses then ++ -- This skill's own mod list and the coupling surface are unchanged since the ++ -- capture pass, so its results cannot have changed: merge the cached passes ++ for _, pass in ipairs(cachedPasses) do ++ mergePass(pass) ++ end ++ elseif enabled then ++ local ownRef ++ if canCacheSkill and cacheStore and fullDPSCache.capture and activeSkill.baseSkillModList then ++ -- Reference the skill's pre-perform mod list for later input diffing ++ ownRef = { } ++ for i, mod in ipairs(activeSkill.baseSkillModList) do ++ ownRef[i] = mod ++ end ++ end + fullEnv.player.mainSkill = activeSkill + calcs.perform(fullEnv, true) + usedEnv = fullEnv ++ -- Capture this pass's results into a plain snapshot, then merge it into the totals; ++ -- the snapshot lets later calls reuse the results when this skill's inputs are unchanged + local skillName = calcs.getActiveSkillDisplayName(activeSkill) +- local minionName = nil ++ local dotCanStack = activeSkill.activeEffect.statSet.skillFlags.DotCanStack ++ local pass = { actors = { } } ++ local minionOut + if activeSkill.minion or usedEnv.minion then +- if usedEnv.minion.output.TotalDPS and usedEnv.minion.output.TotalDPS > 0 then +- minionName = (activeSkill.minion and activeSkill.minion.minionData.name..": ") or (usedEnv.minion and usedEnv.minion.minionData.name..": ") or "" +- t_insert(fullDPS.skills, { name = skillName, dps = usedEnv.minion.output.TotalDPS, count = activeSkillCount, trigger = activeSkill.infoTrigger, skillPart = minionName..activeSkill.skillPartName }) +- fullDPS.combinedDPS = fullDPS.combinedDPS + usedEnv.minion.output.TotalDPS * activeSkillCount +- end +- if usedEnv.minion.output.BleedDPS and usedEnv.minion.output.BleedDPS > fullDPS.bleedDPS then +- fullDPS.bleedDPS = usedEnv.minion.output.BleedDPS +- bleedSource = skillName +- end +- if usedEnv.minion.output.IgniteDPS and usedEnv.minion.output.IgniteDPS > fullDPS.igniteDPS then +- fullDPS.igniteDPS = usedEnv.minion.output.IgniteDPS +- igniteSource = skillName +- end +- if usedEnv.minion.output.PoisonDPS and usedEnv.minion.output.PoisonDPS > fullDPS.poisonDPS then +- fullDPS.poisonDPS = usedEnv.minion.output.PoisonDPS +- poisonSource = skillName +- end +- if usedEnv.minion.output.ImpaleDPS and usedEnv.minion.output.ImpaleDPS > 0 then +- fullDPS.impaleDPS = fullDPS.impaleDPS + usedEnv.minion.output.ImpaleDPS * activeSkillCount +- end +- if usedEnv.minion.output.DecayDPS and usedEnv.minion.output.DecayDPS > 0 then +- fullDPS.decayDPS = fullDPS.decayDPS + usedEnv.minion.output.DecayDPS +- end +- if usedEnv.minion.output.TotalDot and usedEnv.minion.output.TotalDot > 0 then +- fullDPS.dotDPS = fullDPS.dotDPS + usedEnv.minion.output.TotalDot +- end +- if usedEnv.minion.output.CullMultiplier and usedEnv.minion.output.CullMultiplier > 1 and usedEnv.minion.output.CullMultiplier > fullDPS.cullingMulti then +- fullDPS.cullingMulti = usedEnv.minion.output.CullMultiplier +- end ++ minionOut = captureFields(usedEnv.minion.output) ++ local minionNamePrefix = (activeSkill.minion and activeSkill.minion.minionData.name..": ") or (usedEnv.minion and usedEnv.minion.minionData.name..": ") or "" ++ t_insert(pass.actors, { ++ out = minionOut, ++ name = skillName, ++ count = activeSkillCount, ++ trigger = activeSkill.infoTrigger, ++ skillPart = minionNamePrefix .. activeSkill.skillPartName, ++ sourceName = skillName, ++ dotScale = 1, ++ }) + -- This is a fix to prevent Absolution spell hit from being counted multiple times when increasing minions count + if activeSkill.activeEffect.grantedEffect.name == "Absolution" and fullEnv.modDB:Flag(false, "Condition:AbsolutionSkillDamageCountedOnce") then + activeSkillCount = 1 +@@ -222,87 +386,34 @@ function calcs.calcFullDPS(build, mode, override, specEnv) + end + end + ++ local playerOut = captureFields(usedEnv.player.output) + if activeSkill.mirage then + local mirageCount = (activeSkill.mirage.count or 1) * activeSkillCount +- if activeSkill.mirage.output.TotalDPS and activeSkill.mirage.output.TotalDPS > 0 then +- t_insert(fullDPS.skills, { name = activeSkill.mirage.name .. " (Mirage)", dps = activeSkill.mirage.output.TotalDPS, count = mirageCount, trigger = activeSkill.mirage.infoTrigger, skillPart = activeSkill.mirage.skillPartName }) +- fullDPS.combinedDPS = fullDPS.combinedDPS + activeSkill.mirage.output.TotalDPS * mirageCount +- end +- if activeSkill.mirage.output.BleedDPS and activeSkill.mirage.output.BleedDPS > fullDPS.bleedDPS then +- fullDPS.bleedDPS = activeSkill.mirage.output.BleedDPS +- bleedSource = activeSkill.activeEffect.grantedEffect.name .. " (Mirage)" +- end +- if activeSkill.mirage.output.IgniteDPS and activeSkill.mirage.output.IgniteDPS > fullDPS.igniteDPS then +- fullDPS.igniteDPS = activeSkill.mirage.output.IgniteDPS +- igniteSource = activeSkill.activeEffect.grantedEffect.name .. " (Mirage)" +- end +- if activeSkill.mirage.output.PoisonDPS and activeSkill.mirage.output.PoisonDPS > fullDPS.poisonDPS then +- fullDPS.poisonDPS = activeSkill.mirage.output.PoisonDPS +- poisonSource = activeSkill.activeEffect.grantedEffect.name .. " (Mirage)" +- end +- if activeSkill.mirage.output.ImpaleDPS and activeSkill.mirage.output.ImpaleDPS > 0 then +- fullDPS.impaleDPS = fullDPS.impaleDPS + activeSkill.mirage.output.ImpaleDPS * mirageCount +- end +- if activeSkill.mirage.output.DecayDPS and activeSkill.mirage.output.DecayDPS > 0 then +- fullDPS.decayDPS = fullDPS.decayDPS + activeSkill.mirage.output.DecayDPS +- end +- -- This will only take skillFlags from main env. Needs rework if trigger section is to be kept. +- if activeSkill.mirage.output.TotalDot and activeSkill.mirage.output.TotalDot > 0 and (activeSkill.activeEffect.statSet.skillFlags.DotCanStack or (usedEnv.player.output.TotalDot and usedEnv.player.output.TotalDot == 0)) then +- fullDPS.dotDPS = fullDPS.dotDPS + activeSkill.mirage.output.TotalDot * (activeSkill.activeEffect.statSet.skillFlags.DotCanStack and mirageCount or 1) +- end +- if activeSkill.mirage.output.CullMultiplier and activeSkill.mirage.output.CullMultiplier > 1 and activeSkill.mirage.output.CullMultiplier > fullDPS.cullingMulti then +- fullDPS.cullingMulti = activeSkill.mirage.output.CullMultiplier +- end +- if activeSkill.mirage.output.BurningGroundDPS and activeSkill.mirage.output.BurningGroundDPS > fullDPS.burningGroundDPS then +- fullDPS.burningGroundDPS = activeSkill.mirage.output.BurningGroundDPS +- burningGroundSource = activeSkill.activeEffect.grantedEffect.name .. " (Mirage)" +- end +- if activeSkill.mirage.output.CausticGroundDPS and activeSkill.mirage.output.CausticGroundDPS > fullDPS.causticGroundDPS then +- fullDPS.causticGroundDPS = activeSkill.mirage.output.CausticGroundDPS +- causticGroundSource = activeSkill.activeEffect.grantedEffect.name .. " (Mirage)" +- end ++ t_insert(pass.actors, { ++ out = captureFields(activeSkill.mirage.output), ++ name = activeSkill.mirage.name .. " (Mirage)", ++ count = mirageCount, ++ trigger = activeSkill.mirage.infoTrigger, ++ skillPart = activeSkill.mirage.skillPartName, ++ sourceName = skillName .. " (Mirage)", ++ dotScale = (dotCanStack or (playerOut.TotalDot and playerOut.TotalDot == 0)) and (dotCanStack and mirageCount or 1) or nil, ++ }) + end + +- if usedEnv.player.output.TotalDPS and usedEnv.player.output.TotalDPS > 0 then +- t_insert(fullDPS.skills, { name = skillName, dps = usedEnv.player.output.TotalDPS, count = activeSkillCount, trigger = activeSkill.infoTrigger, skillPart = minionName and activeSkill.infoMessage2 or activeSkill.skillPartName }) +- fullDPS.combinedDPS = fullDPS.combinedDPS + usedEnv.player.output.TotalDPS * activeSkillCount +- end +- if usedEnv.player.output.BleedDPS and usedEnv.player.output.BleedDPS > fullDPS.bleedDPS then +- fullDPS.bleedDPS = usedEnv.player.output.BleedDPS +- bleedSource = skillName +- end +- if usedEnv.player.output.CorruptingBloodDPS and usedEnv.player.output.CorruptingBloodDPS > fullDPS.corruptingBloodDPS then +- fullDPS.corruptingBloodDPS = usedEnv.player.output.CorruptingBloodDPS +- corruptingBloodSource = skillName +- end +- if usedEnv.player.output.IgniteDPS and usedEnv.player.output.IgniteDPS > fullDPS.igniteDPS then +- fullDPS.igniteDPS = usedEnv.player.output.IgniteDPS +- igniteSource = skillName +- end +- if usedEnv.player.output.BurningGroundDPS and usedEnv.player.output.BurningGroundDPS > fullDPS.burningGroundDPS then +- fullDPS.burningGroundDPS = usedEnv.player.output.BurningGroundDPS +- burningGroundSource = skillName +- end +- if usedEnv.player.output.PoisonDPS and usedEnv.player.output.PoisonDPS > fullDPS.poisonDPS then +- fullDPS.poisonDPS = usedEnv.player.output.PoisonDPS +- poisonSource = skillName +- end +- if usedEnv.player.output.CausticGroundDPS and usedEnv.player.output.CausticGroundDPS > fullDPS.causticGroundDPS then +- fullDPS.causticGroundDPS = usedEnv.player.output.CausticGroundDPS +- causticGroundSource = skillName +- end +- if usedEnv.player.output.ImpaleDPS and usedEnv.player.output.ImpaleDPS > 0 then +- fullDPS.impaleDPS = fullDPS.impaleDPS + usedEnv.player.output.ImpaleDPS * activeSkillCount +- end +- if usedEnv.player.output.DecayDPS and usedEnv.player.output.DecayDPS > 0 then +- fullDPS.decayDPS = fullDPS.decayDPS + usedEnv.player.output.DecayDPS +- end +- -- This will only take skillFlags from main env. Needs rework. +- if usedEnv.player.output.TotalDot and usedEnv.player.output.TotalDot > 0 then +- fullDPS.dotDPS = fullDPS.dotDPS + usedEnv.player.output.TotalDot * (activeSkill.activeEffect.statSet.skillFlags.DotCanStack and activeSkillCount or 1) +- end +- if usedEnv.player.output.CullMultiplier and usedEnv.player.output.CullMultiplier > 1 and usedEnv.player.output.CullMultiplier > fullDPS.cullingMulti then +- fullDPS.cullingMulti = usedEnv.player.output.CullMultiplier ++ local minionContributed = minionOut and minionOut.TotalDPS and minionOut.TotalDPS > 0 ++ t_insert(pass.actors, { ++ out = playerOut, ++ name = skillName, ++ count = activeSkillCount, ++ trigger = activeSkill.infoTrigger, ++ skillPart = minionContributed and activeSkill.infoMessage2 or activeSkill.skillPartName, ++ sourceName = skillName, ++ dotScale = dotCanStack and activeSkillCount or 1, ++ }) ++ mergePass(pass) ++ if cacheStore and fullDPSCache.capture and ownRef then ++ cacheStore.snapshots[uuid] = { pass } ++ cacheStore.refs[uuid] = ownRef + end + + -- Re-Build env calculator for new run +@@ -321,27 +432,27 @@ function calcs.calcFullDPS(build, mode, override, specEnv) + -- Re-Add ailment DPS components + fullDPS.TotalDotDPS = 0 + if fullDPS.bleedDPS > 0 then +- t_insert(fullDPS.skills, { name = "Best Bleed DPS", dps = fullDPS.bleedDPS, count = 1, source = bleedSource }) ++ t_insert(fullDPS.skills, { name = "Best Bleed DPS", dps = fullDPS.bleedDPS, count = 1, source = sources.bleedDPS or "" }) + fullDPS.TotalDotDPS = fullDPS.TotalDotDPS + fullDPS.bleedDPS + end + if fullDPS.corruptingBloodDPS > 0 then +- t_insert(fullDPS.skills, { name = "Best Corr. Blood DPS", dps = fullDPS.corruptingBloodDPS, count = 1, source = corruptingBloodSource }) ++ t_insert(fullDPS.skills, { name = "Best Corr. Blood DPS", dps = fullDPS.corruptingBloodDPS, count = 1, source = sources.corruptingBloodDPS or "" }) + fullDPS.TotalDotDPS = fullDPS.TotalDotDPS + fullDPS.corruptingBloodDPS + end + if fullDPS.igniteDPS > 0 then +- t_insert(fullDPS.skills, { name = "Best Ignite DPS", dps = fullDPS.igniteDPS, count = 1, source = igniteSource }) ++ t_insert(fullDPS.skills, { name = "Best Ignite DPS", dps = fullDPS.igniteDPS, count = 1, source = sources.igniteDPS or "" }) + fullDPS.TotalDotDPS = fullDPS.TotalDotDPS + fullDPS.igniteDPS + end + if fullDPS.burningGroundDPS > 0 then +- t_insert(fullDPS.skills, { name = "Best Burning Ground DPS", dps = fullDPS.burningGroundDPS, count = 1, source = burningGroundSource }) ++ t_insert(fullDPS.skills, { name = "Best Burning Ground DPS", dps = fullDPS.burningGroundDPS, count = 1, source = sources.burningGroundDPS or "" }) + fullDPS.TotalDotDPS = fullDPS.TotalDotDPS + fullDPS.burningGroundDPS + end + if fullDPS.poisonDPS > 0 then +- t_insert(fullDPS.skills, { name = "Best Poison DPS", dps = fullDPS.poisonDPS, count = 1, source = poisonSource }) ++ t_insert(fullDPS.skills, { name = "Best Poison DPS", dps = fullDPS.poisonDPS, count = 1, source = sources.poisonDPS or "" }) + fullDPS.TotalDotDPS = fullDPS.TotalDotDPS + fullDPS.poisonDPS + end + if fullDPS.causticGroundDPS > 0 then +- t_insert(fullDPS.skills, { name = "Best Caustic Ground DPS", dps = fullDPS.causticGroundDPS, count = 1, source = causticGroundSource }) ++ t_insert(fullDPS.skills, { name = "Best Caustic Ground DPS", dps = fullDPS.causticGroundDPS, count = 1, source = sources.causticGroundDPS or "" }) + fullDPS.TotalDotDPS = fullDPS.TotalDotDPS + fullDPS.causticGroundDPS + end + if fullDPS.impaleDPS > 0 then +@@ -466,7 +577,7 @@ function calcs.buildOutput(build, mode) + for _, skillEffect in ipairs(activeSkill.effectList) do + env.skillsUsed[skillEffect.grantedEffect.name] = true + end +- if activeSkill.minion then ++ if activeSkill.minion and activeSkill.minion.activeSkillList then + for _, activeSkill in ipairs(activeSkill.minion.activeSkillList) do + env.skillsUsed[activeSkill.activeEffect.grantedEffect.id] = true + end +@@ -575,7 +686,7 @@ function calcs.buildOutput(build, mode) + addTo(env.tagTypesUsed, tag.type, mod) + end + end +- if activeSkill.minion then ++ if activeSkill.minion and activeSkill.minion.activeSkillList then + for _, activeSkill in pairs(activeSkill.minion.activeSkillList) do + for _, mod in ipairs(activeSkill.baseSkillModList) do + addModTags(env.minion, mod) +``` + +
+ +
Modules/CalcSections.lua (33 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcSections.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcSections.lua +index 4872f63..d885ad0 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcSections.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcSections.lua +@@ -2355,19 +2355,25 @@ return { + { label = "Spectre Ally", haveOutput = "TotalSpectreLife", + { format = "{0:output:TotalSpectreLife}", + { breakdown = "TotalSpectreLife" }, +- { modName = { "TotalSpectreLife", "takenFromSpectresBeforeYou" } }, ++ { modName = { "TotalSpectreLife", "TakenFromSpectresBeforeYou" } }, + }, + }, + { label = "Totem Ally", haveOutput = "TotalTotemLife", + { format = "{0:output:TotalTotemLife}", + { breakdown = "TotalTotemLife" }, +- { modName = { "TotalTotemLife", "takenFromTotemsBeforeYou" } }, ++ { modName = { "TotalTotemLife", "TakenFromTotemsBeforeYou" } }, + }, + }, + { label = "Vaal Rejuv. Totem", haveOutput = "TotalVaalRejuvenationTotemLife", + { format = "{0:output:TotalVaalRejuvenationTotemLife}", + { breakdown = "TotalVaalRejuvenationTotemLife" }, +- { modName = { "TotalVaalRejuvenationTotemLife", "takenFromVaalRejuvenationTotemsBeforeYou", "takenFromTotemsBeforeYou" } }, ++ { modName = { "TotalVaalRejuvenationTotemLife", "takenFromVaalRejuvenationTotemsBeforeYou", "TakenFromTotemsBeforeYou" } }, ++ }, ++ }, ++ { label = "Companion Ally", haveOutput = "TotalCompanionLife", ++ { format = "{0:output:TotalCompanionLife}", ++ { breakdown = "TotalCompanionLife" }, ++ { modName = { "TotalCompanionLife", "TakenFromCompanionBeforeYou", "TakenFromCompanionBeforeYouFromDeflected" } }, + }, + }, + { label = "Soul Link", haveOutput = "AlliedEnergyShield", +``` + +
+ +
Modules/CalcSetup.lua (336 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcSetup.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcSetup.lua +index 62d1f81..5ba7277 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcSetup.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcSetup.lua +@@ -15,6 +15,12 @@ local band = AND64 + + local tempTable1 = { } + ++local function addGrantedPassiveNode(env, node) ++ env.allocNodes[node.id] = env.spec.nodes[node.id] or node -- use the conquered node data, if available ++ env.grantedPassives[node.id] = true ++ env.extraRadiusNodeList[node.id] = nil ++end ++ + -- Initialise modifier database with stats and conditions common to all actors + function calcs.initModDB(env, modDB) + modDB:NewMod("FireResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") +@@ -92,6 +98,7 @@ function calcs.initModDB(env, modDB) + modDB:NewMod("MassiveShrine", "FLAG", true, "Base", { type = "Condition", var = "MassiveShrine" }) + modDB:NewMod("AlchemistsGenius", "FLAG", true, "Base", { type = "Condition", var = "AlchemistsGenius" }) + modDB:NewMod("LuckyHits", "FLAG", true, "Base", { type = "Condition", var = "LuckyHits" }) ++ modDB:NewMod("HeavyStunBuildup", "MORE", 50, "Base", { type = "Condition", var = "Dazed"}) + modDB:NewMod("ColdCannotHeavyStun", "FLAG", true) + modDB:NewMod("Convergence", "FLAG", true, "Base", { type = "Condition", var = "Convergence" }) + modDB:NewMod("PhysicalDamageReduction", "BASE", -15, "Base", { type = "Condition", var = "Crushed" }) +@@ -104,6 +111,53 @@ function calcs.initModDB(env, modDB) + modDB.conditions["Effective"] = env.mode_effective + end + ++local function getCorruptedJewelEffect(env, item, node) ++ if not item or item.type ~= "Jewel" or not item.corrupted or not node or node.containJewelSocket or node.sinister or item.base.subType == "Charm" then ++ return 0 ++ end ++ local rarity = item.rarity:gsub("(%a)(%u*)", function(a, b) return a..string.lower(b) end) ++ return env.modDB.multipliers["Corrupted" .. rarity .. "JewelEffect"] or 0 ++end ++ ++local function runRadiusJewelFunc(rad, node, out, data) ++ local scale = rad.effectScale ++ if not scale or scale == 1 then ++ rad.func(node, out, data) ++ return ++ end ++ ++ local start = #out ++ rad.func(node, out, data) ++ if #out == start then ++ return ++ end ++ ++ local scaledList = new("ModList") ++ for i = start + 1, #out do ++ scaledList:AddMod(out[i]) ++ end ++ for i = #out, start + 1, -1 do ++ t_remove(out, i) ++ end ++ out:ScaleAddList(scaledList, scale) ++ ++ for i = start + 1, #out do ++ local mod = out[i] ++ if mod.parsedLine then ++ local value = mod.value ++ while type(value) == "table" and value.mod do ++ value = value.mod.value ++ end ++ if type(value) == "table" then ++ value = value.value ++ end ++ if type(value) == "number" then ++ mod.parsedLine = mod.parsedLine:gsub("%d*%.?%d+", math.abs(value), 1) ++ end ++ end ++ end ++end ++ + local function refreshJewelStatCache(env) + local normalNode = { type = "Normal" } + local attributeNode = { type = "Normal", isAttribute = true } +@@ -118,9 +172,9 @@ local function refreshJewelStatCache(env) + GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].attributeModList = new("ModList") + GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].notableModList = new("ModList") + end +- rad.func(normalNode, GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].smallModList, rad.data) +- rad.func(attributeNode, GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].attributeModList, rad.data) +- rad.func(notableNode, GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].notableModList, rad.data) ++ runRadiusJewelFunc(rad, normalNode, GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].smallModList, rad.data) ++ runRadiusJewelFunc(rad, attributeNode, GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].attributeModList, rad.data) ++ runRadiusJewelFunc(rad, notableNode, GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId].notableModList, rad.data) + end + end + +@@ -146,18 +200,18 @@ function calcs.buildModListForNode(env, node, incSmallPassiveSkill, includeKeyst + for _, rad in pairs(env.radiusJewelList) do + if rad.type == "Other" and rad.nodes[node.id] and rad.nodes[node.id].type ~= "Mastery" then + if rad.item.baseName:find("Time%-Lost") == nil and rad.item.baseName:find("Timeless Jewel") == nil then +- rad.func(node, modList, rad.data) ++ runRadiusJewelFunc(rad, node, modList, rad.data) + elseif node.type == "Normal" or node.type == "Notable" then + local cache = GlobalCache.cachedData[env.mode].radiusJewelData[rad.nodeId] + if not cache or (cache.hash ~= rad.jewelHash) then + refreshJewelStatCache(env) + end + if node.type == "Normal" and node.isAttribute and cache and #cache.attributeModList > 0 then +- modList:AddList(cache.attributeModList) ++ modList:CopyList(cache.attributeModList) + elseif node.type == "Normal" and not node.isAttribute and cache and #cache.smallModList > 0 then +- modList:AddList(cache.smallModList) ++ modList:CopyList(cache.smallModList) + elseif node.type == "Notable" and cache and #cache.notableModList > 0 then +- modList:AddList(cache.notableModList) ++ modList:CopyList(cache.notableModList) + end + break + end +@@ -179,7 +233,7 @@ function calcs.buildModListForNode(env, node, incSmallPassiveSkill, includeKeyst + -- Run second pass radius jewels + for _, rad in pairs(env.radiusJewelList) do + if rad.nodes[node.id] and rad.nodes[node.id].type ~= "Mastery" and (rad.type == "Threshold" or (rad.type == "Self" and env.allocNodes[node.id]) or (rad.type == "SelfUnalloc" and not env.allocNodes[node.id])) then +- rad.func(node, modList, rad.data) ++ runRadiusJewelFunc(rad, node, modList, rad.data) + end + end + +@@ -312,7 +366,7 @@ function calcs.buildModListForNodeList(env, nodeList, finishJewels, includeKeyst + + -- Finalise radius jewels + for _, rad in pairs(env.radiusJewelList) do +- rad.func(nil, modList, rad.data) ++ runRadiusJewelFunc(rad, nil, modList, rad.data) + if env.mode == "MAIN" then + if not rad.item.jewelRadiusData then + rad.item.jewelRadiusData = { } +@@ -394,6 +448,7 @@ function wipeEnv(env, accelerate) + if not accelerate.skills then + -- Player Active Skills generation + wipeTable(env.player.activeSkillList) ++ env.sourceGemPropertyInfo = { } + + -- Enhances Active Skills with skill ModFlags, KeywordFlags + -- and modifiers that affect skill scaling (e.g., global buffs/effects) +@@ -769,9 +824,15 @@ function calcs.initEnv(build, mode, override, specEnv) + nodes = copyTable(env.spec.allocNodes, true) + end + env.allocNodes = nodes ++ for nodeId, node in pairs(env.allocNodes) do ++ if node.isGrantedPassive and node.isFreeAllocate then ++ env.allocNodes[nodeId] = nil ++ end ++ end + end + + local nodesModsList = calcs.buildModListForNodeList(env, env.allocNodes, true, true) ++ env.useAltGemQualityStats = nodesModsList:Flag(nil, "GemlingQuality") + + if allocatedNotableCount and allocatedNotableCount > 0 then + modDB:NewMod("Multiplier:AllocatedNotable", "BASE", allocatedNotableCount) +@@ -811,6 +872,16 @@ function calcs.initEnv(build, mode, override, specEnv) + + -- Build and merge item modifiers, and create list of radius jewels + if not accelerate.requirementsItems then ++ local grantedNodes = env.spec:CollectGrantedPassiveNodesFromItems(build.itemsTab, env.allocNodes, env.configInput.ignoreJewelLimits, override, nodesModsList) ++ if mode == "MAIN" then ++ if build.spec:SetGrantedPassiveNodes(grantedNodes) then ++ build.itemsTab:UpdateSockets() ++ end ++ end ++ for _, node in pairs(grantedNodes) do ++ addGrantedPassiveNode(env, node) ++ end ++ + local items = {} + local jewelLimits = {} + local giantsBlood = weaponFlagState.giantsBlood +@@ -859,10 +930,13 @@ function calcs.initEnv(build, mode, override, specEnv) + if slot.weaponSet == 2 and build.itemsTab.activeItemSet.useSecondWeaponSet then + slotName = slotName:gsub(" Swap","") + end ++ local node = slot.nodeId and env.spec.nodes[slot.nodeId] + if slot.nodeId then + -- Slot is a jewel socket, check if socket is allocated + if not env.allocNodes[slot.nodeId] then + goto continue ++ elseif item and not build.itemsTab:IsItemValidForSlot(item, slot.slotName) then ++ goto continue + elseif item then + if item.jewelData then + item.jewelData.limitDisabled = nil +@@ -888,7 +962,7 @@ function calcs.initEnv(build, mode, override, specEnv) + jewelLimits[limitKey] = (jewelLimits[limitKey] or 0) + 1 + end + end +- if item and ( item.jewelRadiusIndex or (override and override.extraJewelFuncs and #override.extraJewelFuncs > 0) ) then ++ if item and not (node and node.sinister) and ( item.jewelRadiusIndex or (override and override.extraJewelFuncs and #override.extraJewelFuncs > 0) ) then + -- Jewel has a radius, add it to the list + local funcList = (item.jewelData and item.jewelData.funcList) or { { type = "Self", func = function(node, out, data) + -- Default function just tallies all stats in radius +@@ -899,7 +973,6 @@ function calcs.initEnv(build, mode, override, specEnv) + end + end } } + for _, func in ipairs(funcList) do +- local node = env.spec.nodes[slot.nodeId] + t_insert(env.radiusJewelList, { + nodes = node.nodesInRadius and node.nodesInRadius[item.jewelRadiusIndex] or { }, + func = func.func, +@@ -921,7 +994,6 @@ function calcs.initEnv(build, mode, override, specEnv) + end + end + for _, funcData in ipairs(override and override.extraJewelFuncs and override.extraJewelFuncs:List({item = item}, "ExtraJewelFunc") or {}) do +- local node = env.spec.nodes[slot.nodeId] + local radius + for index, data in pairs(data.jewelRadius) do + if funcData.radius == data.label then +@@ -954,6 +1026,14 @@ function calcs.initEnv(build, mode, override, specEnv) + ::continue:: + end + ++ for _, rad in ipairs(env.radiusJewelList) do ++ local effect = getCorruptedJewelEffect(env, rad.item, env.spec.nodes[rad.nodeId]) ++ if effect ~= 0 then ++ rad.effectScale = 1 + effect ++ rad.jewelHash = tostring(rad.jewelHash or getHashFromString(rad.item.modSource..rad.item.raw)) .. ":" .. rad.effectScale ++ end ++ end ++ + if not env.configInput.ignoreItemDisablers then + local itemDisabled = {} + local itemDisablers = {} +@@ -963,7 +1043,7 @@ function calcs.initEnv(build, mode, override, specEnv) + for _, slot in pairs(build.itemsTab.orderedSlots) do + local slotName = slot.slotName + if items[slotName] then +- local srcList = items[slotName].modList or items[slotName].slotModList[slot.slotNum] ++ local srcList = items[slotName].modList or items[slotName].slotModList[slot.slotNum] or {} + for _, mod in ipairs(srcList) do + -- checks if it disables another slot + for _, tag in ipairs(mod) do +@@ -1011,6 +1091,7 @@ function calcs.initEnv(build, mode, override, specEnv) + for _, slot in pairs(build.itemsTab.orderedSlots) do + local slotName = slot.slotName + local item = items[slotName] ++ local node = slot.nodeId and env.spec.nodes[slot.nodeId] + if item and item.type == "Flask" then + if slot.active then + env.flasks[item] = true +@@ -1041,8 +1122,7 @@ function calcs.initEnv(build, mode, override, specEnv) + scale = parentItem.socketedJewelEffectModifier + end + end +- if slot.nodeId and item and item.type == "Jewel" and item.jewelData and item.jewelData.jewelIncEffectFromClassStart then +- local node = env.spec.nodes[slot.nodeId] ++ if slot.nodeId and item and item.type == "Jewel" and item.jewelData and item.jewelData.jewelIncEffectFromClassStart and not (node and node.sinister) then + if node and node.distanceToClassStart then + scale = scale + node.distanceToClassStart * (item.jewelData.jewelIncEffectFromClassStart / 100) + end +@@ -1050,7 +1130,6 @@ function calcs.initEnv(build, mode, override, specEnv) + + local addSourceSlotNum = false + if slot.nodeId and item and item.type == "Jewel" then +- local node = env.spec.nodes[slot.nodeId] + if node and node.containJewelSocket then + addSourceSlotNum = true + local inc = node.modList:Sum("INC", nil, "SocketedJewelEffect") +@@ -1062,6 +1141,7 @@ function calcs.initEnv(build, mode, override, specEnv) + env.player.itemList[slotName] = item + -- Merge mods for this item + local srcList = item.modList or (item.slotModList and item.slotModList[slot.slotNum]) or {} ++ local corruptedJewelEffect = slot.nodeId and getCorruptedJewelEffect(env, item, node) or 0 + + -- Remove Spirit Base if CannotGainSpiritFromEquipment flag is true + if nodesModsList:Flag(nil, "CannotGainSpiritFromEquipment") then +@@ -1259,8 +1339,8 @@ function calcs.initEnv(build, mode, override, specEnv) + env.itemModDB:ScaleAddMod(mod, scale) + end + end +- elseif env.modDB.multipliers["Corrupted" .. item.rarity:gsub("(%a)(%u*)", function(a, b) return a..string.lower(b) end) .. "JewelEffect"] and item.type == "Jewel" and item.corrupted and slot.nodeId and item.base.subType ~= "Charm" and not env.spec.nodes[slot.nodeId].containJewelSocket then +- scale = scale + env.modDB.multipliers["Corrupted" .. item.rarity:gsub("(%a)(%u*)", function(a, b) return a..string.lower(b) end) .. "JewelEffect"] ++ elseif corruptedJewelEffect ~= 0 then ++ scale = scale + corruptedJewelEffect + local combinedList = new("ModList") + for _, mod in ipairs(srcList) do + combinedList:MergeMod(mod) +@@ -1325,11 +1405,10 @@ function calcs.initEnv(build, mode, override, specEnv) + -- Add granted passives (e.g., amulet anoints) + if not accelerate.nodeAlloc then + for _, passive in pairs(env.modDB:List(nil, "GrantedPassive")) do +- local node = env.spec.tree.notableMap[passive] +- if node and (not override.removeNodes or not override.removeNodes[node.id]) then +- env.allocNodes[node.id] = env.spec.nodes[node.id] or node -- use the conquered node data, if available +- env.grantedPassives[node.id] = true +- env.extraRadiusNodeList[node.id] = nil ++ for _, node in ipairs(env.spec:ResolveGrantedPassiveNodes(passive)) do ++ if node and (not override.removeNodes or not override.removeNodes[node.id]) then ++ addGrantedPassiveNode(env, node) ++ end + end + end + end +@@ -1372,10 +1451,20 @@ function calcs.initEnv(build, mode, override, specEnv) + local modList = env.player.itemList["Weapon 2"].modList + for _, mod in ipairs(modList) do + local modCopy = copyTable(mod) +- modCopy.source = "Many Sources:" .. tostring(quiverEffectMod * 100) .. "% Quiver Bonus Effect" ++ modCopy.source = "Many Sources:".. colorCodes.SOURCE .. tostring(quiverEffectMod * 100) .. "% Quiver Bonus Effect" + modDB:ScaleAddMod(modCopy, quiverEffectMod) + end + end ++ ++ if env.player.itemList["Amulet"] and env.player.itemList["Amulet"].type == "Amulet" then ++ local amuletEffectMod = env.modDB:Sum("INC", nil, "EffectOfBonusesFromAmulet") / 100 ++ local modList = env.player.itemList["Amulet"].modList ++ for _, mod in ipairs(modList) do ++ local modCopy = copyTable(mod) ++ modCopy.source = "Many Sources:".. colorCodes.SOURCE .. tostring(amuletEffectMod * 100) .. "% Amulet Bonus Effect" ++ modDB:ScaleAddMod(modCopy, amuletEffectMod) ++ end ++ end + + -- Find skills granted by tree nodes + if not accelerate.nodeAlloc then +@@ -1445,6 +1534,7 @@ function calcs.initEnv(build, mode, override, specEnv) + activeGemInstance.enableGlobal1 = true + activeGemInstance.noSupports = grantedSkill.noSupports + group.noSupports = grantedSkill.noSupports ++ activeGemInstance.noReservation = grantedSkill.noReservation + activeGemInstance.triggered = grantedSkill.triggered + activeGemInstance.triggerChance = grantedSkill.triggerChance + wipeTable(group.gemList) +@@ -1677,6 +1767,7 @@ function calcs.initEnv(build, mode, override, specEnv) + level = value.level, + quality = 0, + enabled = true, ++ isSupporting = { }, + }) + end + end +``` + +
+ +
Modules/CalcTools.lua (29 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcTools.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcTools.lua +index cda906b..a44b7b6 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcTools.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcTools.lua +@@ -135,12 +135,20 @@ function calcLib.getGemStatRequirement(level, multi, isSupport) + end + + -- Build table of stats for the given skill instance statset +-function calcLib.buildSkillInstanceStats(skillInstance, grantedEffect, statSet) ++function calcLib.buildSkillInstanceStats(skillInstance, grantedEffect, statSet, includeAltQualityStats) + local stats = { } +- if skillInstance.quality > 0 and grantedEffect.qualityStats then ++ if skillInstance.quality > 0 then + local qualityStats = grantedEffect.qualityStats +- for _, stat in ipairs(qualityStats) do +- stats[stat[1]] = (stats[stat[1]] or 0) + math.modf(stat[2] * skillInstance.quality) ++ if qualityStats then ++ for _, stat in ipairs(qualityStats) do ++ stats[stat[1]] = (stats[stat[1]] or 0) + math.modf(stat[2] * skillInstance.quality) ++ end ++ end ++ qualityStats = grantedEffect.altQualityStats ++ if includeAltQualityStats and qualityStats then ++ for _, stat in ipairs(qualityStats) do ++ stats[stat[1]] = (stats[stat[1]] or 0) + math.modf(stat[2] * skillInstance.quality) ++ end + end + end + local grantedEffectLevel = grantedEffect.levels[skillInstance.level] or { } +``` + +
+ +
Modules/CalcTriggers.lua (31 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcTriggers.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcTriggers.lua +index 975ad5d..5dafee8 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/CalcTriggers.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/CalcTriggers.lua +@@ -429,7 +429,7 @@ local function defaultTriggerHandler(env, config) + end + + -- Dual wield triggers +- if trigRate and source and env.player.weaponData1.type and env.player.weaponData2.type and not source.skillData.doubleHitsWhenDualWielding and (source.skillTypes[SkillType.Melee] or source.skillTypes[SkillType.Attack]) and actor.mainSkill.triggeredBy and actor.mainSkill.triggeredBy.grantedEffect.support and actor.mainSkill.triggeredBy.grantedEffect.fromItem then ++ if trigRate and source and env.player.weaponData1.type and env.player.weaponData2.type and not source.skillData.combinesHitsWhenDualWielding and (source.skillTypes[SkillType.Melee] or source.skillTypes[SkillType.Attack]) and actor.mainSkill.triggeredBy and actor.mainSkill.triggeredBy.grantedEffect.support and actor.mainSkill.triggeredBy.grantedEffect.fromItem then + trigRate = trigRate / 2 + if breakdown then + t_insert(breakdown.EffectiveSourceRate, 2, s_format("/ 2 ^8(due to dual wielding)")) +@@ -722,7 +722,7 @@ local function defaultTriggerHandler(env, config) + local sourceHitChance = GlobalCache.cachedData[env.mode][uuid].HitChance or 0 + if sourceHitChance ~= 100 then + -- Some skills hit with both weapons at the same time. Each weapon rolls accuracy and crit independently +- if source and env.player.weaponData1.type and env.player.weaponData2.type and source.skillData.doubleHitsWhenDualWielding then ++ if source and env.player.weaponData1.type and env.player.weaponData2.type and source.skillData.combinesHitsWhenDualWielding then + local mainHandHit = GlobalCache.cachedData[env.mode][uuid].Env.player.output.MainHand.HitChance + local offHandHit = GlobalCache.cachedData[env.mode][uuid].Env.player.output.OffHand.HitChance + local bothHit = mainHandHit * offHandHit / 100 +@@ -747,7 +747,7 @@ local function defaultTriggerHandler(env, config) + local sourceCritChance = GlobalCache.cachedData[env.mode][uuid].CritChance or 0 + if sourceCritChance ~= 100 then + -- Some skills hit with both weapons at the same time. Each weapon rolls accuracy and crit independently +- if source and env.player.weaponData1.type and env.player.weaponData2.type and source.skillData.doubleHitsWhenDualWielding then ++ if source and env.player.weaponData1.type and env.player.weaponData2.type and source.skillData.combinesHitsWhenDualWielding then + local mainHandCrit = GlobalCache.cachedData[env.mode][uuid].Env.player.output.MainHand.CritChance + local offHandCrit = GlobalCache.cachedData[env.mode][uuid].Env.player.output.OffHand.CritChance + local bothHit = mainHandCrit * offHandCrit / 100 +``` + +
+ +
Modules/ModParser.lua (292 diff lines) + +```diff +diff --git a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/ModParser.lua b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/ModParser.lua +index 3dd2c16..52fa3b4 100644 +--- a/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/a82a33b4fbdb7306ed93159664f7abe2c6819413/src/Modules/ModParser.lua ++++ b/Users/wuyong/codes/game/pobr/.claude/worktrees/agent-af7756e8e082bac60/.cache/vendor-delta/ce8bffaba31f8e68cfce70579e1c96465e7c133c/src/Modules/ModParser.lua +@@ -897,6 +897,7 @@ local modNameList = { + ["effect of socketed abyss jewels"] = "SocketedJewelEffect", + ["effect of socketed soul cores"] = "SocketedSoulCoreEffect", + ["effect of socketed runes"] = "SocketedRuneEffect", ++ ["effect of socketed augment items"] = "SocketedAugmentItemEffect", + ["to inflict fire exposure on hit"] = "FireExposureChance", + ["to apply fire exposure on hit"] = "FireExposureChance", + ["to inflict cold exposure on hit"] = "ColdExposureChance", +@@ -1037,6 +1038,10 @@ local modFlagList = { + ["with ranged weapons"] = { flags = bor(ModFlag.WeaponRanged, ModFlag.Hit) }, + -- Skill types + ["spell"] = { flags = ModFlag.Spell }, ++ ["fire spell"] = { flags = ModFlag.Spell, keywordFlags = KeywordFlag.Fire }, ++ ["cold spell"] = { flags = ModFlag.Spell, keywordFlags = KeywordFlag.Cold }, ++ ["lightning spell"] = { flags = ModFlag.Spell, keywordFlags = KeywordFlag.Lightning }, ++ ["chaos spell"] = { flags = ModFlag.Spell, keywordFlags = KeywordFlag.Chaos }, + ["for spells"] = { flags = ModFlag.Spell }, + ["for spell skills"] = { flags = ModFlag.Spell }, + ["for spell damage"] = { flags = ModFlag.Spell }, +@@ -1285,7 +1290,7 @@ local preFlagList = { + ["^attacks used by ballistas [hd][ae][va][el] "] = { flags = ModFlag.Attack, keywordFlags = KeywordFlag.Totem, tag = { type = "Condition", var = "BallistaSkill" } }, + ["^attack skills [hd][ae][va][el] "] = { keywordFlags = KeywordFlag.Attack }, + ["^spells [hgdf][aei][ivar][nel] a? ?"] = { flags = ModFlag.Spell }, +- ["^spells which cost life gain "] = { keywordFlags = KeywordFlag.Spell, tag = { type = "StatThreshold", stat = "LifeCost", threshold = 1 } }, ++ ["^spells which cost life gain "] = { keywordFlags = KeywordFlag.Spell, tag = { type = "StatThreshold", statList = { "LifeCost", "LifePerSecondCost" }, threshold = 1 } }, + ["^spell skills [hd][ae][va][el] "] = { keywordFlags = KeywordFlag.Spell }, + ["^spell hits [ghd][ae][iva][eln] "] = { flags = ModFlag.Hit, keywordFlags = KeywordFlag.Spell }, + ["^offering skills [hd][ae][va][el] "] = { tag = { type = "SkillType", skillType = SkillType.Offering } }, +@@ -1472,6 +1477,7 @@ local modTagList = { + ["per level"] = { tag = { type = "Multiplier", var = "Level" } }, + ["per player level"] = { tag = { type = "Multiplier", var = "Level" } }, + ["per (%d+) player levels"] = function(num) return { tag = { type = "Multiplier", var = "Level", div = num } } end, ++ ["per (%d+) levels"] = function(num) return { tag = { type = "Multiplier", var = "Level", div = num } } end, + ["per defiance"] = { tag = { type = "Multiplier", var = "Defiance" } }, + ["per (%d+)%% (%a+) effect on enemy"] = function(num, _, effectName) return { tag = { type = "Multiplier", var = firstToUpper(effectName) .. "Effect", div = num, actor = "enemy" } } end, + ["per socketed rune or soul core"] = { tag = { type = "Multiplier", var = "RunesSocketedIn{SlotName}" } }, +@@ -1562,8 +1568,6 @@ local modTagList = { + ["per animated weapon"] = { tag = { type = "Multiplier", var = "AnimatedWeapon", actor = "parent" } }, + ["per grasping vine"] = { tag = { type = "Multiplier", var = "GraspingVinesCount" } }, + ["per fragile regrowth"] = { tag = { type = "Multiplier", var = "FragileRegrowthCount" } }, +- ["per bark"] = { tag = { type = "Multiplier", var = "BarkskinStacks" } }, +- ["per bark below maximum"] = { tag = { type = "Multiplier", var = "MissingBarkskinStacks" } }, + ["per allocated mastery passive skill"] = { tag = { type = "Multiplier", var = "AllocatedMastery" } }, + ["per allocated notable passive skill"] = { tag = { type = "Multiplier", var = "AllocatedNotable" } }, + ["for each connected notable passive skill allocated"] = { tag = { type = "Multiplier", var = "AllocatedConnectedNotable" } }, +@@ -1691,8 +1695,8 @@ local modTagList = { + ["while affected by a normal abyss jewel"] = { tag = { type = "MultiplierThreshold", var = "NormalAbyssJewels", threshold = 1 } }, + ["while an enemy with an open weakness is in your presence"] = { tag = { type = "Condition", var = "OpenWeaknessEnemyPresence" } }, -- This one means there's an enemy that has open weakness "nearby" + ["against enemies with an open weakness"] = { tag = { type = "Condition", var = "EnemyHasOpenWeakness" } }, -- This one means the enemy you're targeting has open weakness +- ["with skills that cost life"] = { tag = { type = "StatThreshold", stat = "LifeCost", threshold = 1 } }, +- ["with spells that cost life"] = { keywordFlags = KeywordFlag.Spell, tag = { type = "StatThreshold", stat = "LifeCost", threshold = 1 } }, ++ ["with skills that cost life"] = { tag = { type = "StatThreshold", statList = { "LifeCost", "LifePerSecondCost" }, threshold = 1 } }, ++ ["with spells that cost life"] = { keywordFlags = KeywordFlag.Spell, tag = { type = "StatThreshold", statList = { "LifeCost", "LifePerSecondCost" }, threshold = 1 } }, + -- Gem conditions + ["if you have at least (%d+) (%a+) support gems socketed"] = function(count, _, color) return { tag = { type = "MultiplierThreshold", var = firstToUpper(color) .. "SupportGems", threshold = count } } end, + ["if you have at least (%d+) red, green and blue support gems socketed"] = function(count) +@@ -1866,6 +1870,7 @@ local modTagList = { + ["during any life flask effect"] = { tag = { type = "Condition", var = "UsingLifeFlask" } }, + ["if you've used a life flask in the past 10 seconds"] = { tag = { type = "Condition", var = "UsingLifeFlask" } }, + ["if you've used a mana flask in the past 10 seconds"] = { tag = { type = "Condition", var = "UsingManaFlask" } }, ++ ["if you've used a mana flask recently"] = { tag = { type = "Condition", var = "UsingManaFlask" } }, + ["while you have no life flask uses left"] = { tag = { type = "Condition", var = "NoLifeFlaskUsesLeft" } }, + ["during effect of any life or mana flask"] = { tag = { type = "Condition", varList = { "UsingManaFlask", "UsingLifeFlask" } } }, + ["while on consecrated ground"] = { tag = { type = "Condition", var = "OnConsecratedGround" } }, +@@ -3326,9 +3331,6 @@ local specialModList = { + mod("DamageTakenOverTime", "MORE", -num * tonumber(duration) / 10, { type = "Condition", var = "HeartstopperAVERAGE" }) + } end, + -- Warden +- ["prevent %+(%d+)%% of suppressed spell damage per bark below maximum"] = function(num) return { +- mod("SpellSuppressionEffect", "BASE", num, { type = "Multiplier", var = "MissingBarkskinStacks" }) +- } end, + ["hits that would ignite instead scorch"] = { flag("IgniteCanScorch"), flag("CannotIgnite") }, + ["you can inflict an additional scorch on each enemy"] = { flag("ScorchCanStack"), mod("ScorchStacksMax", "BASE", 1) }, + ["maximum effect of shock is (%d+)%% increased damage taken"] = function (num) return { mod("ShockMax", "OVERRIDE", num) } end, +@@ -3348,6 +3350,7 @@ local specialModList = { + ["gain maximum life instead of maximum energy shield from equipped armour items"] = { flag("ConvertArmourESToLife") }, + -- Mercenary - Gemling + ["attribute requirements of gems can be satisi?fied by your highest attribute"] = { flag("GemAttributeRequirementsSatisfiedByHighestAttribute") }, ++ ["gem quality grants socketed skills an additional effect"] = { flag("GemlingQuality") }, + ["you can use two copies of the same support gem in different skills"] = { mod("MaxSupportGemCopies", "OVERRIDE", 2) }, + ["you can use each type of support gem an additional time in different skills"] = { mod("MaxSupportGemCopies", "OVERRIDE", 2) }, + ["skills have (%d+)%% increased critical hit chance per connected blue support gem"] = function(num) return { +@@ -3366,7 +3369,9 @@ local specialModList = { + ["blue: skills have (%d+)%% less cost"] = function(count) return { + mod("ManaCost", "MORE", -count, { type = "Condition", var = "MostNumerousBlueSocketedSupports" }) + } end, +- ++ ["green: (%d+)%% less movement speed penalty from using skills while moving"] = function(num) return { ++ mod("MovementSpeedPenalty", "MORE", -num, { type = "Condition", var = "MostNumerousGreenSocketedSupports" }) ++ } end, + -- Monk - Stormweaver + ["targets can be affected by two of your shocks at the same time"] = { flag("ShockCanStack"), mod("ShockStacksMax", "OVERRIDE", 2) }, + ["targets can be affected by two of your chills at the same time"] = { flag("ChillCanStack"), mod("ChillStacksMax", "OVERRIDE", 2) }, +@@ -3440,6 +3445,8 @@ local specialModList = { + } end, + ["only soul cores can be socketed in this item"] = { flag("SocketedSoulCoresOnly") }, + ["only runes can be socketed in this item"] = { flag("SocketedRunesOnly") }, ++ ["this item gains bonuses from socketed items as though it was a? ?(.+)"] = { }, -- Handled in Item.lua ++ ["this item gains bonuses from socketed soul cores as though it was also a? ?(.+)"] = { }, -- Handled in Item.lua + ["has (%d+) sockets?"] = function(num) return { mod("SocketCount", "BASE", num) } end, + ["no physical damage"] = { mod("WeaponData", "LIST", { key = "PhysicalMin" }), mod("WeaponData", "LIST", { key = "PhysicalMax" }), mod("WeaponData", "LIST", { key = "PhysicalDPS" }) }, + ["cannot load or fire ammunition"] = { mod("WeaponData", "LIST", { key = "cannotUseGemTag", value = "ammunition" }) }, +@@ -3814,6 +3821,8 @@ local specialModList = { + mod("EnemyModifier", "LIST", { mod = mod("DamageTaken", "INC", num) }, { type = "ActorCondition", actor = "enemy", var = "Poisoned" }), + mod("EnemyModifier", "LIST", { mod = mod("DamageTaken", "INC", num) }, { type = "ActorCondition", actor = "enemy", var = "Electrocuted" }), + } end, ++ ["non%-channelling spells have (%d+)%% increased magnitude of ailments per (%d+) maximum life"] = function(num, _, div) return { mod("AilmentMagnitude", "INC", num, nil, 0, KeywordFlag.Spell, { type = "SkillType", skillType = SkillType.Channel, neg = true }, { type = "PerStat", stat = "Life", div = tonumber(div) }) } end, ++ ["non%-channelling spells have (%d+)%% reduced magnitude of ailments per (%d+) maximum life"] = function(num, _, div) return { mod("AilmentMagnitude", "INC", -num, nil, 0, KeywordFlag.Spell, { type = "SkillType", skillType = SkillType.Channel, neg = true }, { type = "PerStat", stat = "Life", div = tonumber(div) }) } end, + -- Elemental Ailments + ["enemies take (%d+)%% increased damage for each elemental ailment type among your ailments on them"] = function(num) return { + mod("EnemyModifier", "LIST", { mod = mod("DamageTaken", "INC", num) }, { type = "ActorCondition", actor = "enemy", var = "Frozen" }), +@@ -3896,6 +3905,7 @@ local specialModList = { + ["your chaos damage can ignite"] = { flag("ChaosCanIgnite") }, + ["chaos damage can ignite, chill and shock"] = { flag("ChaosCanIgnite"), flag("ChaosCanChill"), flag("ChaosCanShock") }, + ["your physical damage can chill"] = { flag("PhysicalCanChill") }, ++ ["physical damage from hits contributes to chill magnitude and freeze buildup"] = { flag("PhysicalCanChill"), flag("PhysicalCanFreeze") }, + ["your physical damage can shock"] = { flag("PhysicalCanShock") }, + ["your physical damage can freeze"] = { flag("PhysicalCanFreeze") }, + ["your lightning damage can freeze"] = { flag("LightningCanFreeze") }, +@@ -5335,8 +5345,10 @@ local specialModList = { + ["(%d+)%% increased armour per second you've been stationary, up to a maximum of (%d+)%%"] = function(num, _, limit) return { + mod("Armour", "INC", num, { type = "Multiplier", var = "StationarySeconds", limit = tonumber(limit / num) }, { type = "Condition", var = "Stationary" }), + } end, +- ["(%d+)%% of damage from hits is taken from your spectres' life before you"] = function(num) return { mod("takenFromSpectresBeforeYou", "BASE", num) } end, +- ["(%d+)%% of damage from hits is taken from your nearest totem's life before you"] = function(num) return { mod("takenFromTotemsBeforeYou", "BASE", num, { type = "Condition", var = "HaveTotem" }) } end, ++ ["(%d+)%% of damage from hits is taken from your spectres' life before you"] = function(num) return { mod("TakenFromSpectresBeforeYou", "BASE", num) } end, ++ ["(%d+)%% of damage from hits is taken from your nearest totem's life before you"] = function(num) return { mod("TakenFromTotemsBeforeYou", "BASE", num, { type = "Condition", var = "HaveTotem" }) } end, ++ ["(%d+)%% of damage from hits is taken from your damageable companion's life before you"] = function(num) return { mod("TakenFromCompanionBeforeYou", "BASE", num) } end, ++ ["(%d+)%% of damage from deflected hits is taken from damageable companion's life before you"] = function(num) return { mod("TakenFromCompanionBeforeYouFromDeflected", "BASE", num) } end, + ["(%a+) resistance cannot be penetrated"] = function(_, res) return { flag("EnemyCannotPen"..(res:gsub("^%l", string.upper)).."Resistance") } end, + ["your base energy shield recharge delay is (%d+) seconds"] = function(num) return { mod("EnergyShieldRechargeBase", "OVERRIDE", num), } end, + -- Knockback +@@ -5547,6 +5559,10 @@ local specialModList = { + -- Misc + ["fully broken armour effects also apply to fire damage taken from hits"] = { flag("ArmourBreakFireDamageTaken"), }, + ["fully broken armour you inflict also increases fire damage taken from hits"] = { flag("ArmourBreakFireDamageTaken"), }, ++ ["fully broken armour you inflict also increases cold and lightning damage taken from hits"] = { ++ flag("ArmourBreakColdDamageTaken"), ++ flag("ArmourBreakLightningDamageTaken"), ++ }, + ["can't use c?h?e?s?t? ?b?o?d?y? ?armour"] = { mod("CanNotUseBody", "Flag", 1, { type = "DisablesItem", slotName = "Body Armour" }) }, + --["can't use helmets"] = { mod("CanNotUseHelmet", "Flag", 1, { type = "DisablesItem", slotName = "Helmet" }) }, -- this one does not work due to being on a passive? + ["can't use helmet"] = { mod("CanNotUseHelmet", "Flag", 1, { type = "DisablesItem", slotName = "Helmet" }) }, -- this is to allow for custom mod without saying the other is parsed +@@ -5579,7 +5595,7 @@ local specialModList = { + ["(%d+)%% increased accuracy rating against enemies you mark"] = function(num) return { mod("AccuracyVsEnemy", "INC", num, { type = "ActorCondition", actor = "enemy", var = "Marked" } ) } end, + ["(%d+)%% more accuracy rating against enemies you mark"] = function(num) return { mod("AccuracyVsEnemy", "MORE", num, { type = "ActorCondition", actor = "enemy", var = "Marked" } ) } end, + ["%+(%d+) to accuracy against bleeding enemies"] = function(num) return { mod("AccuracyVsEnemy", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Bleeding" } ) } end, +- ["cannot recover energy shield to above armour"] = { flag("ArmourESRecoveryCap") }, ++ ["y?o?u? ?cannot recover energy shield to above armour"] = { flag("ArmourESRecoveryCap") }, + ["cannot recover energy shield to above evasion rating"] = { flag("EvasionESRecoveryCap") }, + ["warcries empower (%d+) additional attacks?"] = function(num) return { mod("ExtraEmpoweredAttacks", "BASE", num) } end, + ["warcries empower an additional attack"] = function(num) return { mod("ExtraEmpoweredAttacks", "BASE", 1) } end, +@@ -5781,8 +5797,8 @@ local specialModList = { + flag("CannotHeavyStun"), + flag("CannotPin"), + }, +- ["immobilise enemies at (%d+)%% buildup instead of (%d+)%%"] = function(num, _, base) return { +- mod("EnemyModifier", "LIST", { mod = mod("PoiseThreshold", "MORE",-num) }), ++ ["immobilise enemies at (%d+)%% buildup instead of (%d+)%%"] = function(num) return { ++ mod("EnemyModifier", "LIST", { mod = mod("PoiseThreshold", "MORE",(num - 100)) }), + } end, + ["the effect of blind on you is reversed"] = { flag("BlindEffectReversed") }, + ["blind does not affect your chance to hit"] = { flag("IgnoreBlindHitChance") }, +@@ -5838,6 +5854,7 @@ local specialModList = { + ["rage grants spell damage instead of attack damage"] = { flag("Condition:RageSpellDamage") }, + ["inherent loss of rage is (%d+)%% slower"] = function(num) return { mod("InherentRageLoss", "INC", -num) } end, + ["inherent loss of rage is (%d+)%% faster"] = function(num) return { mod("InherentRageLoss", "INC", num) } end, ++ ["no inherent loss of rage"] = { flag("InherentRageLossIsPrevented") }, + ["inherent rage loss starts (%d+) seconds? later"] = function(num) return { mod("InherentRageLossDelay", "BASE", num) } end, + ["your critical damage bonus is (%d+)%%"] = function(num) return { mod("CritMultiplier", "OVERRIDE", num) } end, + ["base critical hit chance for attacks with weapons is ([%d%.]+)%%"] = function(num) return { mod("WeaponBaseCritChance", "OVERRIDE", num) } end, +@@ -5850,6 +5867,7 @@ local specialModList = { + mod("CritChanceCap", "OVERRIDE", num), + } end, + ["allocates (.+) if you have the matching modifiers? on forbidden (.+)"] = function(_, ascendancy, side) return { mod("GrantedAscendancyNode", "LIST", { side = side, name = ascendancy }) } end, ++ ["allocates (%d+) sinister jewel sockets?"] = function(num) return { mod("GrantedPassive", "LIST", { type = "SinisterJewelSockets", count = num }) } end, + ["allocates (.+)"] = function(_, passive) return { mod("GrantedPassive", "LIST", passive) } end, + ["battlemage"] = { flag("Battlemage"), mod("MainHandWeaponDamageAppliesToSpells", "MAX", 100) }, + ["transfiguration of body"] = { flag("TransfigurationOfBody") }, +@@ -6008,6 +6026,9 @@ local specialModList = { + ["attack skills cost life instead of (%d+)%% of mana cost"] = function(num) return { + mod("HybridManaAndLifeCost_Life", "BASE", num, nil, ModFlag.Attack) + } end, ++ ["skills from corrupted gems have (%d+)%% of mana costs converted to life costs"] = function(num) return { ++ mod("HybridManaAndLifeCost_Life", "BASE", num, { type = "Condition", var = "GemCorrupted" }), ++ } end, + ["non%-channelling spells cost an additional (%d+)%% of your maximum life"] = function(num) return { + mod("LifeCostBase", "BASE", 1, nil, 0, KeywordFlag.Spell, { type = "PercentStat", percent = num, stat = "Life", floor = true }, { type = "SkillType", skillType = SkillType.Channel, neg = true }) + } end, +@@ -6076,11 +6097,12 @@ local specialModList = { + mod("LightningMax", "BASE", 1, { type = "PercentStat", stat = "Mana" , percent = num }, { type = "SkillType", skillType = SkillType.Attack }), + } end, + ["(%d+)%% reduced movement speed penalty from using skills while moving"] = function(num) return { mod("MovementSpeedPenalty", "INC", -num) } end, ++ ["(%d+)%% reduced movement speed penalty while actively blocking"] = function(num) return { mod("MovementSpeedPenalty", "INC", -num, { type = "SkillType", skillType = SkillType.ActiveBlock }) } end, + ["(%d+)%% less movement speed penalty from using skills while moving"] = function(num) return { mod("MovementSpeedPenalty", "MORE", -num) } end, + ["no movement speed penalty while shield is raised"] = function(num) return { +- mod("MovementSpeedPenalty", "MORE", -100, { type = "SkillName", skillName = "Raise Shield"}) ++ mod("MovementSpeedPenalty", "MORE", -100, { type = "SkillType", skillType = SkillType.ActiveBlock }) + } end, +- -- Conditional Player Quantity / Rarity ++ -- Conditional Player Quantity / Rarity + ["(%d+)%% increased quantity of items dropped by slain normal enemies"] = function(num) return { mod("LootQuantityNormalEnemies", "INC", num) } end, + ["(%d+)%% increased rarity of items dropped by slain magic enemies"] = function(num) return { mod("LootRarityMagicEnemies", "INC", num) } end, + -- Skill-specific enchantment modifiers +@@ -6216,13 +6238,13 @@ end + -- NOTE: conditional mods with "Immune to ..." cannot be handled for PoE2 as they no longer start with "You are..." or similar prefixes that trigger a "FLAG" mod + specialModList["immune to (.-) w?h?i[lf]e? (.*)"] = = function(_, debuff, cond) + -- NOTE: this only handles cases for which unconditional immunity mods exist to avoid false positives that don't actually get calculated +- ++ + -- look for static or dynamically phrased base immunity mod + local searchPrefix1 = "immun[ei]t?y? to " .. ailment and string.lower(debuff) + local searchPrefix2 = "immune to " .. ailment and string.lower(debuff) + local lowerAilment = ailment and string.lower(ailment) or "" + local validDebuff = (specialModList[searchPrefix1 .. lowerAilment] or specialModList[searchPrefix2 .. lowerAilment]) and true or false +- ++ + -- look if condition exists + -- todo make more dynamic + local tagKey = (validDebuff and cond) and "while " .. string.lower(cond) +@@ -6412,7 +6434,7 @@ local deprecatedSkillNames = { ["Flammability"] = true } + for gemId, gemData in pairs(data.gems) do + local grantedEffect = gemData.grantedEffect + local skillName = grantedEffect.baseTypeName or grantedEffect.name +- if not grantedEffect.hidden and not grantedEffect.support and not deprecatedSkillNames[skillName] then ++ if not grantedEffect.hidden and not grantedEffect.support and not grantedEffect.fromItem and not deprecatedSkillNames[skillName] then + skillNameList[" "..skillName:lower().." "] = { tag = { type = "SkillName", skillName = skillName, includeTransfigured = true } } + preSkillNameList["^"..skillName:lower().." "] = { tag = { type = "SkillName", skillName = skillName, includeTransfigured = true } } + preSkillNameList["^"..skillName:lower().." has ?a? "] = { tag = { type = "SkillName", skillName = skillName, includeTransfigured = true } } +@@ -6764,7 +6786,7 @@ local function parseMod(line, order) + effect = getEffectFromStatus(effectLine) + effect = combineToUpper(effect) + end +- ++ + if type(effect) == "table" then + modName = { effect[1] .. "Immune", effect[2] .. "Immune" } + modType = { type(modValue) == "table" and modValue.type or "FLAG", type(modValue) == "table" and modValue.type or "FLAG" } +@@ -7019,14 +7041,17 @@ local jewelOtherFuncs = { + ["^(%w+) Passive Skills in Radius also grant (.*)$"] = function(passiveType, mod) + return function(node, out, data) + if node and (node.type == firstToUpper(passiveType) or (node.type == "Normal" and not node.isAttribute and firstToUpper(passiveType) == "Small")) then +- local modList, line = parseMod(mod) +- if not line and modList[1] then -- something failed to parse, do not add to list +- modList[1].parsedLine = capitalizeWordsInString(mod) +- modList[1].source = data.modSource +- if type(modList[1].value) == "table" and modList[1].value.mod then +- modList[1].value.mod.source = data.modSource ++ local modList, extra = parseMod(mod) ++ -- avoid adding mods if mod line wasn't parsed correctly ++ if not extra and modList[1] then ++ for _, modListMod in ipairs(modList) do ++ modListMod.parsedLine = capitalizeWordsInString(mod) ++ modListMod.source = data.modSource ++ if type(modListMod.value) == "table" and modListMod.value.mod then ++ modListMod.value.mod.source = data.modSource ++ end ++ out:AddMod(modListMod) + end +- out:AddMod(modList[1]) + end + end + end +@@ -7034,14 +7059,17 @@ local jewelOtherFuncs = { + ["conquered (%w+) Passive Skills also grant (.*)$"] = function(passiveType, mod) + return function(node, out, data) + if node and (node.type == firstToUpper(passiveType) or (node.type == "Normal" and not node.isAttribute and firstToUpper(passiveType) == "Small") or (node.type == "Normal" and node.isAttribute and firstToUpper(passiveType) == "Attribute")) then +- local modList, line = parseMod(mod) +- if not line and modList[1] then -- something failed to parse, do not add to list +- modList[1].parsedLine = capitalizeWordsInString(mod) +- modList[1].source = data.modSource +- if type(modList[1].value) == "table" and modList[1].value.mod then +- modList[1].value.mod.source = data.modSource ++ local modList, extra = parseMod(mod) ++ -- avoid adding mods if mod line wasn't parsed correctly ++ if not extra and modList[1] then ++ for _, modListMod in ipairs(modList) do ++ modListMod.parsedLine = capitalizeWordsInString(mod) ++ modListMod.source = data.modSource ++ if type(modListMod.value) == "table" and modListMod.value.mod then ++ modListMod.value.mod.source = data.modSource ++ end ++ out:AddMod(modListMod) + end +- out:AddMod(modList[1]) + end + end + end +``` + +
+ +## Data module diff + +`src/Data/` + `src/Modules/Data*.lua` — data tables; ranked by churn, no hunks. + +| file | +added | -removed | churn | +|---|---:|---:|---:| +| `Data/StatDescriptions/stat_descriptions.lua` | 13822 | 13341 | 27163 | +| `Data/TimelessJewelData/LegionPassives.lua` | 11131 | 11129 | 22260 | +| `Data/FlavourText.lua` | 1893 | 1891 | 3784 | +| `Data/Spectres.lua` | 3507 | 6 | 3513 | +| `Data/ModItemExclusive.lua` | 1551 | 1518 | 3069 | +| `Data/Skills/spectre.lua` | 2798 | 3 | 2801 | +| `Data/ModRunes.lua` | 934 | 360 | 1294 | +| `Data/Skills/SkillAssets.lua` | 618 | 615 | 1233 | +| `Data/ModItem.lua` | 571 | 570 | 1141 | +| `Data/Skills/act_int.lua` | 788 | 350 | 1138 | +| `Data/TradeSiteStats.lua` | 882 | 176 | 1058 | +| `Data/Skills/other.lua` | 610 | 298 | 908 | +| `Data/Skills/act_str.lua` | 603 | 196 | 799 | +| `Data/StatDescriptions/passive_skill_stat_descriptions.lua` | 277 | 307 | 584 | +| `Data/Skills/act_dex.lua` | 356 | 143 | 499 | +| `Data/ModVeiled.lua` | 250 | 248 | 498 | +| `Data/Bases/shield.lua` | 228 | 228 | 456 | +| `Data/QueryMods.lua` | 313 | 94 | 407 | +| `Data/ModJewel.lua` | 173 | 173 | 346 | +| `Data/ModCache.lua` | 99 | 84 | 183 | +| `Data/StatDescriptions/Specific_Skill_Stat_Descriptions/queens_procession_reservation.lua` | 178 | 0 | 178 | +| `Data/Minions.lua` | 128 | 15 | 143 | +| `Data/StatDescriptions/Specific_Skill_Stat_Descriptions/abyssal_living_bomb.lua` | 141 | 0 | 141 | +| `Data/Skills/minion.lua` | 121 | 16 | 137 | +| `Data/Bases/belt.lua` | 59 | 59 | 118 | +| `Modules/Data.lua` | 84 | 11 | 95 | +| `Modules/Data.lua` | 84 | 11 | 95 | +| `Data/Skills/sup_int.lua` | 82 | 4 | 86 | +| `Data/Bases/body.lua` | 43 | 43 | 86 | +| `Data/SkillStatMap.lua` | 61 | 16 | 77 | +| `Data/Bases/helmet.lua` | 38 | 38 | 76 | +| `Data/Bases/gloves.lua` | 43 | 32 | 75 | +| `Data/Bases/boots.lua` | 33 | 33 | 66 | +| `Data/StatDescriptions/Specific_Skill_Stat_Descriptions/abyssal_pact.lua` | 65 | 0 | 65 | +| `Data/Bases/ring.lua` | 29 | 29 | 58 | +| `Data/ModCorrupted.lua` | 27 | 27 | 54 | +| `Data/Bases/sceptre.lua` | 20 | 20 | 40 | +| `Data/Skills/sup_str.lua` | 39 | 0 | 39 | +| `Data/ModScalability.lua` | 32 | 5 | 37 | +| `Data/Rares.lua` | 17 | 17 | 34 | + +## Extracted overlay diff + +`extract-lua` run against both pins; entry-level add/remove/change per collection. +A section missing below means extraction failed on the *old* pin (incompatible +Lua structure) — see the soft-step summary at the end. + +### `--what special-mods` + +**`entries`** — +13 added, -86 removed, ~4 changed +- added: `vnd_allocates_d_sinister_jewel_sockets_054c1300`, `vnd_d_of_damage_from_deflected_hits_is_taken_dcecf2f5`, `vnd_d_of_damage_from_hits_is_taken_from_your_2f80a1e8`, `vnd_d_reduced_movement_speed_penalty_while_a_bf805056`, `vnd_fully_broken_armour_you_inflict_also_inc_4ed5b5e2`, `vnd_gem_quality_grants_socketed_skills_an_ad_5fbefb66`, `vnd_green_d_less_movement_speed_penalty_from_81bf43df`, `vnd_no_inherent_loss_of_rage_7e3c829b`, `vnd_physical_damage_from_hits_contributes_to_c39e2da0`, `vnd_skills_from_corrupted_gems_have_d_of_man_883f84e0`, `vnd_this_item_gains_bonuses_from_socketed_it_a3f2b519`, `vnd_this_item_gains_bonuses_from_socketed_so_8371786d`, `vnd_y_o_u_cannot_recover_energy_shield_to_ab_d449ee1c` +- removed: `vnd_bow_shot_fires_an_additional_projectile_ccb5e788`, `vnd_bow_shot_fires_d_additional_projectiles_d25e690b`, `vnd_bow_shot_fires_d_additional_shard_projec_b2369979`, `vnd_bow_shot_pierces_an_additional_target_5faa16a9`, `vnd_bow_shot_pierces_d_additional_targets_bf9ac4c1`, `vnd_cannot_recover_energy_shield_to_above_ar_58066d9b`, `vnd_chaos_bolt_fires_an_additional_projectil_be9f3572`, `vnd_chaos_bolt_fires_d_additional_projectile_01874b52`, `vnd_chaos_bolt_fires_d_additional_shard_proj_8c5943e9`, `vnd_chaos_bolt_pierces_an_additional_target_b3ef083f`, `vnd_chaos_bolt_pierces_d_additional_targets_a7607329`, `vnd_compose_requiem_fires_an_additional_proj_38de640c`, `vnd_compose_requiem_fires_d_additional_proje_6026db87`, `vnd_compose_requiem_fires_d_additional_shard_a2d29b23`, `vnd_compose_requiem_pierces_an_additional_ta_e980f59a`, `vnd_compose_requiem_pierces_d_additional_tar_9c6c0f1c`, `vnd_crossbow_shot_fires_an_additional_projec_68cd9458`, `vnd_crossbow_shot_fires_d_additional_project_e5a3b9a0`, `vnd_crossbow_shot_fires_d_additional_shard_p_f559f599`, `vnd_crossbow_shot_pierces_an_additional_targ_db22df9c`, `vnd_crossbow_shot_pierces_d_additional_targe_7abffbad`, `vnd_exsanguinate_chains_an_additional_d_time_45448c87`, `vnd_exsanguinate_chains_an_additional_time_02d87466`, `vnd_exsanguinate_chains_d_additional_times_02dee454`, `vnd_firebolt_fires_an_additional_projectile_6a145ddb`, `vnd_firebolt_fires_d_additional_projectiles_4c8d345b`, `vnd_firebolt_fires_d_additional_shard_projec_967b7c2b`, `vnd_firebolt_pierces_an_additional_target_ae776eac`, `vnd_firebolt_pierces_d_additional_targets_f5efa616`, `vnd_freezing_shards_fires_an_additional_proj_cf8b85c5`, `vnd_freezing_shards_fires_d_additional_proje_c5448c6a`, `vnd_freezing_shards_fires_d_additional_shard_16c6dcac`, `vnd_freezing_shards_pierces_an_additional_ta_aef0f52f`, `vnd_freezing_shards_pierces_d_additional_tar_524bbfb6`, `vnd_galvanic_field_chains_an_additional_d_ti_b7051595`, `vnd_galvanic_field_chains_an_additional_time_1cee14d3`, `vnd_galvanic_field_chains_d_additional_times_e3b243eb`, `vnd_grenade_fires_an_additional_projectile_a7877efa`, `vnd_grenade_fires_d_additional_projectiles_2b3d94aa`, `vnd_grenade_fires_d_additional_shard_project_b8c9fa9e` … (+46 more) +- changed: `vnd_d_of_damage_from_hits_is_taken_from_your_0487689a`, `vnd_d_of_damage_from_hits_is_taken_from_your_628fb916`, `vnd_immobilise_enemies_at_d_buildup_instead_609cc61d`, `vnd_no_movement_speed_penalty_while_shield_i_c7772d82` + + +### `--what parser-rules` + +**`flag_phrases`** — +4 added, -0 removed, ~0 changed +- added: `chaos spell`, `cold spell`, `fire spell`, `lightning spell` + +**`name_map`** — +1 added, -0 removed, ~0 changed +- added: `effect of socketed augment items` + +**`pre_flags`** — +0 added, -0 removed, ~1 changed +- changed: `^spells which cost life gain ` + +**`tag_phrases`** — +2 added, -2 removed, ~2 changed +- added: `if you've used a mana flask recently`, `per (%d+) levels` +- removed: `per bark`, `per bark below maximum` +- changed: `with skills that cost life`, `with spells that cost life` + + +### `--what uniques` + +**`uniques`** — +1 added, -1 removed, ~46 changed +- added: `Waistgate` +- removed: `Waistgate Heavy Belt` +- changed: `Adonia's Ego`, `Bijouborne`, `Blessed Bonds`, `Cadiro's Gambit`, `Choir of the Storm`, `Controlled Metamorphosis`, `Cursecarver`, `Darkness Enthroned`, `Eventide Petals`, `Glimpse of Chaos`, `Goregirdle`, `Guiding Palm`, `Guiding Palm of the Eye`, `Guiding Palm of the Heart`, `Guiding Palm of the Mind`, `Heartbound Loop`, `Hinekora's Sight`, `Infernoclasp`, `Keeper of the Arc`, `Lavianga's Spirits`, `Mageblood`, `Mahuxotl's Machination`, `Marohi Erqi`, `Palm of the Dreamer`, `Pragmatism`, `Prism Guardian`, `Rearguard`, `Reverie`, `Rite of Passage`, `Sacred Flame`, `Saffell's Frame`, `Saitha's Spear`, `Sandstorm Visage`, `Sculpted Suffering`, `Strugglescream`, `Svalinn`, `Sylvan's Effigy`, `Temporalis`, `The Burden of Shadows`, `The Covenant` … (+6 more) + + +## Soft-step summary + +All steps succeeded. + diff --git a/pipeline/README.md b/pipeline/README.md index 45681a7c..37612be9 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -42,6 +42,29 @@ cargo run -p pobr-data-adapter -- --raw ./tables --out ../data --patch `./.cache/`(~113MB bundle 索引)、`./tables/`、`./files/` 均为中间物,**已 gitignore,不入库**。 仓库只保存 `config.json`、脚本、本 README,以及第 3 步产出的 `data//*.json`(最小适配数据)。 +## Vendor calc-delta 报告(`diff-vendor-calcs.sh`) + +版本升级后,用它把「这次补丁改了哪些计算公式 / 数据」变成一份 triage 清单, +取代「翻黄金 → 看 parity 暴跌 → 逐 build 考古」的发现流程。 + +```bash +pipeline/diff-vendor-calcs.sh [--out ] +# 默认输出:devs/docs/audits/vendor-delta-.md +``` + +- 两个 vendor pin 各做一次 shallow git checkout 到 `.cache/vendor-delta//` + (gitignore,命中缓存即跳过;用 git checkout 而非 codeload tarball——headless + 抽取的 `HeadlessWrapper.lua` 引导需要完整工作树,tarball 缺文件会导致 + `modLib.parseMod missing`)。 +- 报告三节:**Calc 模块 diff**(`Modules/Calc*.lua` + `ModParser.lua` 的 diffstat + + 折叠 hunks)、**数据模块 diff**(`Data/` + `Modules/Data*.lua` 只给按改动量排名的 + diffstat)、**抽取产物 diff**(对两个 pin 跑 `extract-lua --what + special-mods|parser-rules|uniques`,对生成 JSON 做条目级 add/remove/change 汇总)。 +- 软降级(沿用 `regen-all.sh` 的 `soft_step` 精神):某个 `--what` 在旧 pin 上因 + 结构不兼容跑失败时,报告注明缺失原因,脚本不中止。 +- 旋钮:`MAX_HUNK_LINES`(单文件 hunk 超此行数只留 diffstat,默认 800)、 + `DATA_TOP`(数据文件排名条数,默认 40)。 + ## 扩展 / 升版 - 新 PoE2 版本:更新 `config.json` 的 `patch`,重跑三步,`data/` 下生成新版本目录,`diff` 审查。 diff --git a/pipeline/diff-vendor-calcs.sh b/pipeline/diff-vendor-calcs.sh new file mode 100755 index 00000000..0ac4944f --- /dev/null +++ b/pipeline/diff-vendor-calcs.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash +# diff-vendor-calcs.sh — P0-2: vendor calc-delta report. +# +# Compares two PoB2 vendor pins (PathOfBuilding-PoE2 commits) and emits a +# markdown triage list of what a game patch changed in the calc engine and in +# the extracted overlays. Engine adaptation then starts from a checklist, +# instead of "flip the golden, watch parity drop, oracle-debug build by build". +# +# Usage: +# pipeline/diff-vendor-calcs.sh [--out ] +# +# Default output: devs/docs/audits/vendor-delta-.md +# +# Env knobs: +# MAX_HUNK_LINES=800 per-file calc hunk cap; larger diffs collapse to +# numstat only (keeps the report readable). +# DATA_TOP=40 how many data files to rank by churn. +# +# Deps: git, python3, cargo (+ luajit for extract-lua). Each vendor pin is a +# shallow git checkout in .cache/vendor-delta// (gitignored, cache-hit +# skips re-fetch). Soft-steps (regen-all.sh convention): an extract that fails +# on the *old* pin (incompatible Lua structure) is noted and skipped, the report +# still generates. +set -euo pipefail + +VENDOR_REPO="https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2.git" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CACHE="${ROOT}/.cache/vendor-delta" +HELPER="${ROOT}/pipeline/json_entry_diff.py" +SHIM="${ROOT}/pipeline/lua-shims/lua-utf8.lua" +MAX_HUNK_LINES="${MAX_HUNK_LINES:-800}" +DATA_TOP="${DATA_TOP:-40}" + +# Calc engine files PoBR mirrors: everything Calc* plus the two big non-Calc +# calc files. Data*.lua go in the data section, so exclude them here. +CALC_GLOBS=("Modules/Calc"*.lua "Modules/ModParser.lua") + +SYNC=(cargo run --quiet -p sync-pob-catalog --) +SOFT_FAILURES=() + +usage() { grep -E '^# ' "$0" | sed -E 's/^# ?//'; } + +die() { echo "diff-vendor-calcs: $*" >&2; exit 1; } + +# soft: run cmd; on failure record label and keep going (always returns 0 so +# `set -e` never fires on an expected soft-step failure). +soft() { + local label="$1"; shift + if ! "$@"; then + SOFT_FAILURES+=("${label}") + echo " ⚠ soft-step failed (continuing): ${label}" >&2 + fi + return 0 +} + +# Fetch a pin as a shallow git checkout (driver.sh style). NOT a codeload +# tarball: the headless extractor's HeadlessWrapper.lua bootstrap needs the full +# working tree (e.g. TreeData/*/tree.lua) that codeload strips, so a tarball pin +# yields "modLib.parseMod missing" and the overlay diff can't run. +fetch() { + local sha="$1" + local dir="${CACHE}/${sha}" + if [ -d "${dir}/.git" ] && [ -f "${dir}/src/Modules/ModParser.lua" ]; then + echo " cache hit: ${sha}" >&2 + else + echo " fetching ${sha} (shallow git) ..." >&2 + rm -rf "${dir}"; mkdir -p "${dir}" + git -C "${dir}" init -q + git -C "${dir}" remote add origin "${VENDOR_REPO}" + # The vendor tree carries heavy binary art, so the shallow pack is large and + # flaky over a slow link — retry the transient "early EOF" failures. + local ok=0 attempt + for attempt in 1 2 3; do + if git -C "${dir}" fetch -q --depth 1 origin "${sha}"; then ok=1; break; fi + echo " fetch attempt ${attempt} failed; retrying ..." >&2 + sleep 5 + done + [ "${ok}" -eq 1 ] || die "git fetch failed for ${sha} after 3 attempts (bad sha? network?)" + git -C "${dir}" checkout -q FETCH_HEAD \ + || die "git checkout failed for ${sha}" + fi + # extract-lua's headless bootstrap requires lua-utf8 (C module in vendor); + # drop the tracked pure-Lua shim where PoB expects it (regen-all.sh does this). + local rt="${dir}/runtime/lua" + if [ -d "${rt}" ] && [ ! -f "${rt}/lua-utf8.lua" ] && [ -f "${SHIM}" ]; then + cp "${SHIM}" "${rt}/lua-utf8.lua" + fi +} + +pob_version() { + local mf="${CACHE}/$1/manifest.xml" v="" + [ -f "${mf}" ] && v="$(grep -oE 'Version number="[^"]+"' "${mf}" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" + echo "${v:-unknown}" +} + +# ---- args ---------------------------------------------------------------- +OLD=""; NEW=""; OUT="" +while [ $# -gt 0 ]; do + case "$1" in + --out) OUT="${2:-}"; shift 2 ;; + -h|--help) usage; exit 0 ;; + -*) die "unknown flag: $1" ;; + *) if [ -z "${OLD}" ]; then OLD="$1"; elif [ -z "${NEW}" ]; then NEW="$1"; else die "unexpected arg: $1"; fi; shift ;; + esac +done +[ -n "${OLD}" ] && [ -n "${NEW}" ] || { usage; exit 2; } +[ -f "${HELPER}" ] || die "missing helper: ${HELPER}" +OUT="${OUT:-${ROOT}/devs/docs/audits/vendor-delta-${NEW:0:8}.md}" + +# ---- fetch both pins ----------------------------------------------------- +echo "== [1/4] fetch vendor pins" +fetch "${OLD}" +fetch "${NEW}" +OLD_SRC="${CACHE}/${OLD}/src" +NEW_SRC="${CACHE}/${NEW}/src" +# extract-lua reads a `.pob2-version.txt` next to the vendor tree for _meta; +# the cache layout has none, so write a per-pin file and pass --version-file. +OLD_VF="${CACHE}/${OLD}/.pob2-version.txt"; printf '%s\n' "${OLD}" > "${OLD_VF}" +NEW_VF="${CACHE}/${NEW}/.pob2-version.txt"; printf '%s\n' "${NEW}" > "${NEW_VF}" +OLD_VER="$(pob_version "${OLD}")" +NEW_VER="$(pob_version "${NEW}")" + +# ---- ensure sync-pob-catalog builds (sccache-safe) ----------------------- +echo "== [2/4] build sync-pob-catalog" +if ! cargo build --quiet -p sync-pob-catalog 2>/dev/null; then + echo " build failed; retrying with rustc wrapper disabled (sccache bypass)" >&2 + export CARGO_BUILD_RUSTC_WRAPPER="" + cargo build --quiet -p sync-pob-catalog || die "sync-pob-catalog build failed" +fi + +# ---- report scaffolding -------------------------------------------------- +mkdir -p "$(dirname "${OUT}")" +: > "${OUT}" +w() { printf '%s\n' "$*" >> "${OUT}"; } + +w "# Vendor calc-delta — ${OLD:0:8} → ${NEW:0:8}" +w "" +w "| | old | new |" +w "|---|---|---|" +w "| commit | \`${OLD}\` | \`${NEW}\` |" +w "| PoB2 version | ${OLD_VER} | ${NEW_VER} |" +w "" +w "Generated by \`pipeline/diff-vendor-calcs.sh ${OLD} ${NEW}\`." +w "This is a Phase-1 engine work list: the calc-file hunks and overlay entry" +w "deltas below are what a hand-port must chase before flipping the parity golden." +w "" + +# ---- 3a. calc module diff ------------------------------------------------ +echo "== [3/4] diff calc modules + data" +w "## Calc module diff" +w "" +w "Files PoBR mirrors: \`Modules/Calc*.lua\` + \`Modules/ModParser.lua\`." +w "Full hunks are in the collapsed blocks; a file whose diff exceeds" +w "\`MAX_HUNK_LINES=${MAX_HUNK_LINES}\` shows only its stat line." +w "" + +# collect the union of calc files across both pins +declare -a CALC_FILES=() +for base in "${OLD_SRC}" "${NEW_SRC}"; do + for g in "${CALC_GLOBS[@]}"; do + for f in "${base}/"${g}; do + [ -e "${f}" ] || continue + CALC_FILES+=("${f#"${base}/"}") + done + done +done +# unique, sorted +mapfile -t CALC_FILES < <(printf '%s\n' "${CALC_FILES[@]}" | sort -u) + +w "| file | +added | -removed |" +w "|---|---:|---:|" +declare -a CALC_CHANGED=() +for rel in "${CALC_FILES[@]}"; do + a="${OLD_SRC}/${rel}"; b="${NEW_SRC}/${rel}" + [ -f "${a}" ] || a=/dev/null + [ -f "${b}" ] || b=/dev/null + ns="$(git diff --no-index --numstat "${a}" "${b}" 2>/dev/null || true)" + [ -n "${ns}" ] || continue + add="$(printf '%s' "${ns}" | awk '{print $1}')" + del="$(printf '%s' "${ns}" | awk '{print $2}')" + w "| \`${rel}\` | ${add} | ${del} |" + CALC_CHANGED+=("${rel}") +done +[ ${#CALC_CHANGED[@]} -gt 0 ] || w "| _(no calc files changed)_ | | |" +w "" + +for rel in "${CALC_CHANGED[@]}"; do + a="${OLD_SRC}/${rel}"; b="${NEW_SRC}/${rel}" + [ -f "${a}" ] || a=/dev/null + [ -f "${b}" ] || b=/dev/null + patch="$(git diff --no-index -- "${a}" "${b}" 2>/dev/null || true)" + n="$(printf '%s\n' "${patch}" | wc -l | tr -d ' ')" + w "
${rel} (${n} diff lines)" + w "" + if [ "${n}" -gt "${MAX_HUNK_LINES}" ]; then + w "_Hunks omitted (${n} lines > MAX_HUNK_LINES=${MAX_HUNK_LINES}). Re-run with a higher MAX_HUNK_LINES to inline._" + else + w '```diff' + printf '%s\n' "${patch}" >> "${OUT}" + w '```' + fi + w "" + w "
" + w "" +done + +# ---- 3b. data module diff (stat + churn ranking only) -------------------- +w "## Data module diff" +w "" +w "\`src/Data/\` + \`src/Modules/Data*.lua\` — data tables; ranked by churn, no hunks." +w "" +# numstat over the two Data trees; Modules/Data* handled by a second pass. +data_numstat() { + git diff --no-index --numstat "${OLD_SRC}/Data" "${NEW_SRC}/Data" 2>/dev/null || true + for base in "${OLD_SRC}" "${NEW_SRC}"; do + for f in "${base}/Modules/Data"*.lua; do + [ -e "${f}" ] || continue + rel="${f#"${base}/"}" + a="${OLD_SRC}/${rel}"; b="${NEW_SRC}/${rel}" + [ -f "${a}" ] || a=/dev/null + [ -f "${b}" ] || b=/dev/null + git diff --no-index --numstat "${a}" "${b}" 2>/dev/null || true + done + done +} +w "| file | +added | -removed | churn |" +w "|---|---:|---:|---:|" +rows="$(data_numstat | awk 'NF>=3 && ($1+0>0 || $2+0>0){ch=$1+$2; path=$3; for(i=4;i<=NF;i++)path=path" "$i; print ch"\t"$1"\t"$2"\t"path}' | sort -rn | head -n "${DATA_TOP}")" +if [ -n "${rows}" ]; then + # strip the codeload cache prefix from paths for readability + printf '%s\n' "${rows}" | while IFS=$'\t' read -r ch add del path; do + # collapse git's `{old => new}` rename braces first, then strip the cache + # prefix so only the vendor-relative path (Data/...) remains. + rel="$(printf '%s' "${path}" | sed -E "s#\{[^}]*=> ([^}]*)\}#\1#g; s#.*/${NEW}/src/##; s#.*/${OLD}/src/##")" + w "| \`${rel}\` | ${add} | ${del} | ${ch} |" + done +else + w "| _(no data files changed)_ | | | |" +fi +w "" + +# ---- 4. extracted overlay diff ------------------------------------------- +echo "== [4/4] diff extracted overlays" +w "## Extracted overlay diff" +w "" +w "\`extract-lua\` run against both pins; entry-level add/remove/change per collection." +w "A section missing below means extraction failed on the *old* pin (incompatible" +w "Lua structure) — see the soft-step summary at the end." +w "" +TMP="$(mktemp -d)" +trap 'rm -rf "${TMP}"' EXIT + +extract_diff() { + local what="$1" + local o="${TMP}/${what}-old.json" n="${TMP}/${what}-new.json" + w "### \`--what ${what}\`" + w "" + local ok_old=1 ok_new=1 + "${SYNC[@]}" extract-lua --what "${what}" --vendor-root "${OLD_SRC}" --version-file "${OLD_VF}" --out "${o}" >/dev/null 2>&1 || ok_old=0 + "${SYNC[@]}" extract-lua --what "${what}" --vendor-root "${NEW_SRC}" --version-file "${NEW_VF}" --out "${n}" >/dev/null 2>&1 || ok_new=0 + if [ "${ok_old}" -eq 0 ] || [ "${ok_new}" -eq 0 ]; then + local which="old=${ok_old} new=${ok_new}" + SOFT_FAILURES+=("extract ${what} (${which})") + w "_Skipped — extraction failed (${which}, 1=ok 0=fail). Old-pin failures are" + w "expected when the vendor Lua structure changed across the bump._" + w "" + return 0 + fi + python3 "${HELPER}" "${o}" "${n}" >> "${OUT}" || SOFT_FAILURES+=("diff ${what}") + w "" +} + +for what in special-mods parser-rules uniques; do + extract_diff "${what}" +done + +# ---- soft-step summary --------------------------------------------------- +w "## Soft-step summary" +w "" +if [ ${#SOFT_FAILURES[@]} -eq 0 ]; then + w "All steps succeeded." +else + for f in "${SOFT_FAILURES[@]}"; do w "- ⚠ ${f}"; done +fi +w "" + +echo "report written: ${OUT}" diff --git a/pipeline/json_entry_diff.py b/pipeline/json_entry_diff.py new file mode 100755 index 00000000..e0ca97bc --- /dev/null +++ b/pipeline/json_entry_diff.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Entity-level diff of two extract-lua JSON outputs, for the vendor-delta report. + +Every top-level list (except `_meta`) is treated as an entry collection; the +diff reports added / removed / changed entries *by identity*, so a game patch's +overlay churn reads as "these rules/uniques/special-mods moved", not a wall of +JSON line hunks. Emits markdown to stdout. + +Usage: json_entry_diff.py + json_entry_diff.py --selftest +""" +import hashlib +import json +import sys + +# Identity key priority — first present string/int field names the entry. +# Covers the shapes we diff: special_vendor(`id`), uniques(`name`), +# parser-rules forms(`pattern`)/name_map(`phrase`)/... +ID_KEYS = ["id", "name", "phrase", "pattern", "key", "text", "stat", "line", "raw"] +NAME_CAP = 40 # ponytail: cap listed names; raise if a section needs the full set + + +def identity(entry): + if isinstance(entry, dict): + for k in ID_KEYS: + v = entry.get(k) + if isinstance(v, (str, int)): + return str(v) + return "hash:" + hashlib.sha1(json.dumps(entry, sort_keys=True).encode()).hexdigest()[:12] + return str(entry) + + +def canon(entry): + return json.dumps(entry, sort_keys=True) + + +def collections(doc): + """Top-level lists keyed by name; a bare list doc becomes `(root)`.""" + if isinstance(doc, list): + return {"(root)": doc} + if isinstance(doc, dict): + return {k: v for k, v in doc.items() if k != "_meta" and isinstance(v, list)} + return {} + + +def diff_list(old, new): + om, nm = {}, {} + for e in old: + om.setdefault(identity(e), e) + for e in new: + nm.setdefault(identity(e), e) + added = sorted(k for k in nm if k not in om) + removed = sorted(k for k in om if k not in nm) + changed = sorted(k for k in nm if k in om and canon(nm[k]) != canon(om[k])) + return added, removed, changed + + +def fmt(names): + shown = names[:NAME_CAP] + more = len(names) - len(shown) + s = ", ".join("`%s`" % n for n in shown) + if more > 0: + s += f" … (+{more} more)" + return s + + +def render(old, new): + oc, nc = collections(old), collections(new) + lines = [] + for k in sorted(set(oc) | set(nc)): + a, r, c = diff_list(oc.get(k, []), nc.get(k, [])) + if not (a or r or c): + continue + lines.append(f"**`{k}`** — +{len(a)} added, -{len(r)} removed, ~{len(c)} changed") + if a: + lines.append(f"- added: {fmt(a)}") + if r: + lines.append(f"- removed: {fmt(r)}") + if c: + lines.append(f"- changed: {fmt(c)}") + lines.append("") + return "\n".join(lines) if lines else "_No entry-level changes._" + + +def selftest(): + old = {"_meta": {"vendor_commit": "aaa"}, "forms": [ + {"pattern": "keep", "form": "BASE"}, + {"pattern": "gone", "form": "INC"}, + {"pattern": "edit", "form": "BASE"}, + ]} + new = {"_meta": {"vendor_commit": "bbb"}, "forms": [ + {"pattern": "keep", "form": "BASE"}, + {"pattern": "edit", "form": "MORE"}, # changed + {"pattern": "fresh", "form": "BASE"}, # added + ]} + a, r, c = diff_list(old["forms"], new["forms"]) + assert a == ["fresh"], a + assert r == ["gone"], r + assert c == ["edit"], c + # _meta churn must not register as a change. + assert "No entry-level changes" not in render(old, new) + assert render({"_meta": {"x": 1}}, {"_meta": {"x": 2}}) == "_No entry-level changes._" + print("selftest OK") + + +def main(): + if len(sys.argv) >= 2 and sys.argv[1] == "--selftest": + return selftest() + if len(sys.argv) != 3: + sys.exit("usage: json_entry_diff.py | --selftest") + with open(sys.argv[1]) as f: + old = json.load(f) + with open(sys.argv[2]) as f: + new = json.load(f) + print(render(old, new)) + + +if __name__ == "__main__": + main() From ad92bc5c68c3306f8fc6c0b63e5b18d48554cca9 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 00:26:44 +0800 Subject: [PATCH 12/74] docs: record v0.0.3 bump-cost items as delivered --- docs/version-bump-architecture.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/version-bump-architecture.md b/docs/version-bump-architecture.md index 3d4c58c8..a2c2fbbe 100644 --- a/docs/version-bump-architecture.md +++ b/docs/version-bump-architecture.md @@ -1,7 +1,14 @@ # Why version bumps look like 100k-line changes, and the v0.0.3 plan to fix it -Status: **v0.0.3-dev milestone plan** (2026-07-16). Companion to +Status: **all five items delivered on v0.0.3-dev** (2026-07-17). Companion to `adapting-to-0.5.4b.md` (which tracks the remaining 0.5.4b engine gaps). +Delivery map: P0-1 → `pobr_gamedata::test_pins` + `data//generated/test_pins.json` +(bless: `POBR_BLESS_PINS=1`, wired as regen-all's final step); P0-2 → +`pipeline/diff-vendor-calcs.sh` (sample report: `devs/docs/audits/vendor-delta-ce8bffab.md`, +which retro-identifies Mageblood without parity archaeology); P1-3 → +`data/overlay-common/special_mods.json` (133 entries) with id-override merge in +`GameData::special_mods`; P1-4 → `pipeline/bump-version.sh`; P2-5 → `.gitattributes`. +The acceptance test remains the next real GGG patch (§5). ## 1. Anatomy of the v0.0.1 → v0.0.2 diff (the evidence) From 34553f951b2abf163a5cd5862edd1ce943a34b95 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 11:12:00 +0800 Subject: [PATCH 13/74] feat(gamedata): migrate four curated overlay domains to overlay-common Move buff_definitions, high_precision_mods, local_mods and vendor_name_aliases into version-independent data/overlay-common/ (all four were byte-identical across the two shipped data versions, so both version copies are removed). Loaders inherit the common layer: - buff_definitions: two-layer id-merge like special_mods (common base + version-specific override/append). - high_precision_mods / local_mods: single-object domains, version-first with overlay-common fallback via new GameData::load_overlay_or_common. - vendor_name_aliases has no runtime loader (doc source-of-truth asset only). Extract paths::merge_by_key generic helper, shared by special_mods and buff_definitions, replacing the special_mods-local merge copy. Content-preserving (pure file moves); parity_no_regression unchanged. --- .../src/domains/buff_definitions.rs | 34 +- .../src/domains/high_precision_mods.rs | 5 +- .../pobr-gamedata/src/domains/local_mods.rs | 7 +- .../pobr-gamedata/src/domains/special_mods.rs | 14 +- crates/pobr-gamedata/src/paths.rs | 38 ++ data/4.5.4.3/overlay/buff_definitions.json | 563 ------------------ data/4.5.4.3/overlay/high_precision_mods.json | 57 -- data/4.5.4.3/overlay/local_mods.json | 23 - data/4.5.4.3/overlay/vendor_name_aliases.json | 510 ---------------- .../buff_definitions.json | 0 .../high_precision_mods.json | 0 .../local_mods.json | 0 .../vendor_name_aliases.json | 0 13 files changed, 79 insertions(+), 1172 deletions(-) delete mode 100644 data/4.5.4.3/overlay/buff_definitions.json delete mode 100644 data/4.5.4.3/overlay/high_precision_mods.json delete mode 100644 data/4.5.4.3/overlay/local_mods.json delete mode 100644 data/4.5.4.3/overlay/vendor_name_aliases.json rename data/{4.5.0.3.4/overlay => overlay-common}/buff_definitions.json (100%) rename data/{4.5.0.3.4/overlay => overlay-common}/high_precision_mods.json (100%) rename data/{4.5.0.3.4/overlay => overlay-common}/local_mods.json (100%) rename data/{4.5.0.3.4/overlay => overlay-common}/vendor_name_aliases.json (100%) diff --git a/crates/pobr-gamedata/src/domains/buff_definitions.rs b/crates/pobr-gamedata/src/domains/buff_definitions.rs index b81fc0fa..05c07670 100644 --- a/crates/pobr-gamedata/src/domains/buff_definitions.rs +++ b/crates/pobr-gamedata/src/domains/buff_definitions.rs @@ -10,11 +10,37 @@ use pobr_data::catalog::buffs::BuffDefinitionsDef; use crate::{GameData, LoadError}; impl GameData { - /// 加载内建 buff 定义(恒走 `overlay/` 定位;`_meta` 由 serde 忽略)。 - /// 文件缺失(旧数据包无此 overlay 域)返回 `Ok(None)`——消费侧行为 = - /// 无内建 buff 展开(向后兼容);其余 IO / 解析错误照常上抛,不静默。 + /// 加载内建 buff 定义,**两层合并**:版本无关策展层 + /// `data/overlay-common/buff_definitions.json`(人工归纳,居先打底),叠版本层 + /// `/overlay/buff_definitions.json`(版本特有覆盖)。合并按 `id` 逐条覆盖/ + /// 追加([`crate::paths::merge_by_key`],同 special_mods)——人工归纳的 buff 语义 + /// 随游戏版本不变,放 common 层新版免费继承(`docs/version-bump-architecture.md` + /// P1-3);`_meta` 由 serde 忽略。 + /// + /// 两层皆缺(旧数据包无此 overlay 域)返回 `Ok(None)`——消费侧行为 = 无内建 buff + /// 展开(向后兼容);其余 IO / 解析错误照常上抛,不静默。 pub fn buff_definitions(&self) -> Result, LoadError> { - match self.load_json_at::(self.overlay_path("buff_definitions.json")) { + let common = match self.overlay_common_path("buff_definitions.json") { + Some(path) => self.load_buff_definitions_at(path)?, + None => None, + }; + let version = self.load_buff_definitions_at(self.overlay_path("buff_definitions.json"))?; + Ok(match (common, version) { + (None, None) => None, + (Some(def), None) | (None, Some(def)) => Some(def), + (Some(common), Some(version)) => Some(BuffDefinitionsDef { + buffs: crate::paths::merge_by_key(common.buffs, version.buffs, |b| &b.id), + }), + }) + } + + /// 读一个 `buff_definitions` schema 文件为 `Option`:NotFound → `None`(软降级), + /// 其余错误上抛。`load_json_at` 仍叠加用户 patch 层。 + fn load_buff_definitions_at( + &self, + path: std::path::PathBuf, + ) -> Result, LoadError> { + match self.load_json_at::(path) { Ok(def) => Ok(Some(def)), Err(LoadError::Io { ref source, .. }) if source.kind() == std::io::ErrorKind::NotFound => diff --git a/crates/pobr-gamedata/src/domains/high_precision_mods.rs b/crates/pobr-gamedata/src/domains/high_precision_mods.rs index 0b09a291..e7b1ed68 100644 --- a/crates/pobr-gamedata/src/domains/high_precision_mods.rs +++ b/crates/pobr-gamedata/src/domains/high_precision_mods.rs @@ -10,8 +10,9 @@ use pobr_data::catalog::high_precision_mods::HighPrecisionModsDef; use crate::{GameData, LoadError}; impl GameData { - /// 加载取整精度例外表(单对象域,恒走 `overlay/` 定位,缺文件即报错)。 + /// 加载取整精度例外表(单对象域,版本无关策展):版本 `overlay/` 优先、 + /// `overlay-common/` 兜底([`Self::load_overlay_or_common`]),两层皆缺即报错。 pub fn high_precision_mods(&self) -> Result { - self.load_json_at(self.overlay_path("high_precision_mods.json")) + self.load_overlay_or_common("high_precision_mods.json") } } diff --git a/crates/pobr-gamedata/src/domains/local_mods.rs b/crates/pobr-gamedata/src/domains/local_mods.rs index 1749bcdf..564fd9bd 100644 --- a/crates/pobr-gamedata/src/domains/local_mods.rs +++ b/crates/pobr-gamedata/src/domains/local_mods.rs @@ -11,9 +11,10 @@ use pobr_data::catalog::local_mods::LocalModsDef; use crate::{GameData, LoadError}; impl GameData { - /// 加载局部词条白名单(单对象域,恒走 `overlay/` 定位,缺文件即报错—— - /// 降级语义由消费方裁决,不在 loader 层吞错)。 + /// 加载局部词条白名单(单对象域,版本无关策展):版本 `overlay/` 优先、 + /// `overlay-common/` 兜底([`Self::load_overlay_or_common`]),两层皆缺即报错—— + /// 降级语义由消费方裁决,不在 loader 层吞错。 pub fn local_mods(&self) -> Result { - self.load_json_at(self.overlay_path("local_mods.json")) + self.load_overlay_or_common("local_mods.json") } } diff --git a/crates/pobr-gamedata/src/domains/special_mods.rs b/crates/pobr-gamedata/src/domains/special_mods.rs index 450d98e0..725fee51 100644 --- a/crates/pobr-gamedata/src/domains/special_mods.rs +++ b/crates/pobr-gamedata/src/domains/special_mods.rs @@ -82,16 +82,10 @@ impl GameData { } } -/// 合并策展两层:`common`(版本无关基底)叠 `version`(版本特有覆盖)。version 的每条 -/// 按 `id` 覆盖 common 同 id 条目(整条替换,保持 common 原位);common 无此 id 的 -/// version 条目按 version 出现序追加在末尾。同输入恒同输出(确定性)。 +/// 合并策展两层:`common`(版本无关基底)叠 `version`(版本特有覆盖),按 `id` +/// 逐条覆盖/追加([`crate::paths::merge_by_key`] 的 special_mods 特化)。 fn merge_special_layers(common: SpecialModsDef, version: SpecialModsDef) -> SpecialModsDef { - let mut entries = common.entries; - for v in version.entries { - match entries.iter_mut().find(|e| e.id == v.id) { - Some(slot) => *slot = v, - None => entries.push(v), - } + SpecialModsDef { + entries: crate::paths::merge_by_key(common.entries, version.entries, |e| &e.id), } - SpecialModsDef { entries } } diff --git a/crates/pobr-gamedata/src/paths.rs b/crates/pobr-gamedata/src/paths.rs index 901891f1..6ce95ee4 100644 --- a/crates/pobr-gamedata/src/paths.rs +++ b/crates/pobr-gamedata/src/paths.rs @@ -54,6 +54,44 @@ impl GameData { .parent() .map(|parent| parent.join("overlay-common").join(rel)) } + + /// 加载一个**单对象策展 overlay 域**,版本层优先、版本无关层 + /// `overlay-common/` 兜底:版本 `overlay/` 存在则整份用之(版本特有修正 + /// 的逃生舱),否则读 common(版本目录免费继承,见 `docs/version-bump-architecture.md` + /// P1-3)。两层皆缺 → 版本层的 [`LoadError::Io`](NotFound,路径指向 `overlay/`), + /// 是否降级由消费方裁决。列表域(按 id 逐条覆盖/追加)用 [`merge_by_key`] 而非本方法。 + pub(crate) fn load_overlay_or_common(&self, rel: &str) -> Result + where + T: for<'de> serde::Deserialize<'de>, + { + match self.load_json_at::(self.overlay_path(rel)) { + Err(crate::LoadError::Io { ref source, .. }) + if source.kind() == std::io::ErrorKind::NotFound => + { + match self.overlay_common_path(rel) { + Some(common) => self.load_json_at(common), + // root 无父目录(FS 根)——无 common 层可退,复现版本层 NotFound 给消费方。 + None => self.load_json_at(self.overlay_path(rel)), + } + } + other => other, + } + } +} + +/// 合并两个按稳定 key 索引的策展 overlay 层:`version` 每条按 `key` 覆盖 `common` +/// 同 key 条目(整条替换,保持 common 原位),common 无此 key 的 version 条目按出现序 +/// 追加在末尾。同输入恒同输出(确定性)。人工策展基底放 common,版本特有覆盖放版本层 +/// (见 `docs/version-bump-architecture.md` P1-3)。 +pub(crate) fn merge_by_key(common: Vec, version: Vec, key: impl Fn(&T) -> &str) -> Vec { + let mut entries = common; + for v in version { + match entries.iter_mut().find(|e| key(e) == key(&v)) { + Some(slot) => *slot = v, + None => entries.push(v), + } + } + entries } #[cfg(test)] diff --git a/data/4.5.4.3/overlay/buff_definitions.json b/data/4.5.4.3/overlay/buff_definitions.json deleted file mode 100644 index 3f88e557..00000000 --- a/data/4.5.4.3/overlay/buff_definitions.json +++ /dev/null @@ -1,563 +0,0 @@ -{ - "_meta": { - "extracted_files": [ - "Modules/CalcPerform.lua" - ], - "generator": "manual-curation (00-index 裁决 §4.2-4 批准的 overlay 通道例外)", - "regen_command": "人工归纳(doActorMisc :503-765 过程式 if-chain 无法 luajit 序列化);vendor 升级后跑 `cargo run -p sync-pob-catalog -- check-buff-refs --vendor-root vendor/PathOfBuilding-PoE2/src --defs data/4.5.0.3.4/overlay/buff_definitions.json` 对账,人工复核后 --write 刷新行段 hash", - "schema": "buff_definitions/v1", - "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "2df5a7433dd2f1609e2fad8a6c3c917f923fe34f", - "vendor_commit_subject": "2df5a74 Fix crash when importing a character that uses Facebreaker gloves (#2098)" - }, - "buffs": [ - { - "id": "Adrenaline", - "trigger_flag": "Adrenaline", - "mode_gate": "combat", - "effect": { - "base": 1.0, - "inc_stats": [ - "BuffEffectOnSelf" - ], - "rounding": "none" - }, - "mods": [ - { - "name": "Damage", - "mod_type": "INC", - "value": { - "kind": "scaled_rounded", - "coeff": 100.0, - "rounding": "floor" - } - }, - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "scaled_rounded", - "coeff": 25.0, - "rounding": "floor" - }, - "flags": [ - "Attack" - ] - }, - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "scaled_rounded", - "coeff": 25.0, - "rounding": "floor" - }, - "flags": [ - "Cast" - ] - }, - { - "name": "MovementSpeed", - "mod_type": "INC", - "value": { - "kind": "scaled_rounded", - "coeff": 25.0, - "rounding": "floor" - } - }, - { - "name": "PhysicalDamageReduction", - "mod_type": "BASE", - "value": { - "kind": "scaled_rounded", - "coeff": 10.0, - "rounding": "floor" - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 586, - "line_end": 593, - "segment_hash": "fnv1a64:e3f46c64c93e2f77" - }, - "notes": "vendor 逐 mod m_floor(coeff × effectMod),effect 级不取整" - }, - { - "id": "ArcaneSurge", - "trigger_flag": "Condition:ArcaneSurge", - "mode_gate": "combat", - "effect": { - "base": 1.0, - "inc_stats": [ - "ArcaneSurgeEffect", - "BuffEffectOnSelf" - ], - "rounding": "none" - }, - "mods": [ - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "scaled_rounded", - "coeff": 15.0, - "rounding": "none" - }, - "flags": [ - "Cast" - ] - }, - { - "name": "ManaRegen", - "mod_type": "MORE", - "value": { - "kind": "scaled_rounded", - "coeff": 20.0, - "rounding": "none" - } - } - ], - "conditions_set": [ - "AffectedByArcaneSurge" - ], - "verified": true, - "vendor_ref": { - "file": "Modules/CalcDefence.lua", - "line_start": 1580, - "line_end": 1591, - "segment_hash": "fnv1a64:144064cc425efef2" - }, - "notes": "基本形(默认 15% Cast Speed / 20% more ManaRegen × effect,effect=1+Σinc/100 不取整,vendor 无 floor)。未覆盖分支:Max(ArcaneSurgeCastSpeed/ManaRegen) 覆写、ArcaneSurgeLifeRegen flag(LifeRegen 改道)、ArcaneSurgeDamage(Spell Damage MORE)——均无树/装备样本,出现时升级 handler。" - }, - { - "id": "ChaoticMight", - "trigger_flag": "ChaoticMight", - "mode_gate": "combat", - "effect": { - "base": 1.0, - "inc_stats": [ - "BuffEffectOnSelf" - ], - "rounding": "none" - }, - "mods": [ - { - "name": "PhysicalDamageGainAsChaos", - "mod_type": "BASE", - "value": { - "kind": "scaled_rounded", - "coeff": 30.0, - "rounding": "floor" - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 582, - "line_end": 585, - "segment_hash": "fnv1a64:dbd8f9c8a44c9452" - } - }, - { - "id": "Convergence", - "trigger_flag": "Convergence", - "mode_gate": "combat", - "effect": { - "base": 30.0, - "inc_stats": [ - "BuffEffectOnSelf" - ], - "rounding": "floor" - }, - "mods": [ - { - "name": "ElementalDamage", - "mod_type": "MORE", - "value": { - "kind": "per_effect", - "coeff": 1.0 - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 594, - "line_end": 597, - "segment_hash": "fnv1a64:4abf1b725bc2c12b" - } - }, - { - "id": "Elusive", - "trigger_flag": "Elusive", - "mode_gate": "combat", - "handler_id": "buff:elusive", - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 612, - "line_end": 631, - "segment_hash": "fnv1a64:36b76d63b4ee74c6" - }, - "notes": "真逻辑:Max({source=Skill}) 查询 + Override 上限 + Nightblade 交互 + 输出字段 ElusiveEffectMod" - }, - { - "id": "Fanaticism", - "trigger_flag": "Fanaticism", - "mode_gate": "combat", - "handler_id": "buff:fanaticism", - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 571, - "line_end": 576, - "segment_hash": "fnv1a64:7d71030ed94f4da6" - }, - "notes": "真逻辑:selfCast 门控需 mainSkill.activeEffect.srcInstance 状态;公式 = floor(75×(1+BuffEffectOnSelf/100)) → Speed MORE / Cost INC -e / AoE INC(全 Cast flag)" - }, - { - "id": "Fortify", - "trigger_flag": "Fortified", - "mode_gate": "combat", - "handler_id": "buff:fortify", - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 524, - "line_end": 538, - "segment_hash": "fnv1a64:9e63cf1ecf4d6c25" - }, - "notes": "真逻辑:stacks 模型(Maximum/Minimum/Override Fortification + alliedFortify)→ DamageTakenWhenHit MORE -floor((1+BuffEffectOnSelf/100)×stacks);触发还含 Multiplier:Fortification > 0 分支" - }, - { - "id": "Freeze", - "trigger_flag": "Freeze", - "mode_gate": "combat", - "effect": { - "base": 70.0, - "inc_stats": [ - "SelfChillEffect" - ], - "more_stats": [ - "SelfChillEffect" - ], - "rounding": "floor", - "min": 0.0 - }, - "mods": [ - { - "name": "ActionSpeed", - "mod_type": "INC", - "value": { - "kind": "per_effect", - "coeff": -1.0 - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 684, - "line_end": 687, - "segment_hash": "fnv1a64:b64bb12d1a1a4549" - }, - "notes": "calcLib.mod = (1+ΣINC/100)×ΠMORE;vendor m_max(m_floor(70×mod),0)——floor 在 clamp 前,与 effect 公式序一致" - }, - { - "id": "HerEmbrace", - "trigger_flag": "HerEmbrace", - "mode_gate": "combat", - "mods": [ - { - "name": "AvoidStun", - "mod_type": "BASE", - "value": { - "kind": "literal", - "value": 100.0 - } - }, - { - "name": "PhysicalDamageGainAsFire", - "mod_type": "BASE", - "value": { - "kind": "literal", - "value": 123.0 - }, - "flags": [ - "Sword" - ] - }, - { - "name": "AvoidFreeze", - "mod_type": "BASE", - "value": { - "kind": "literal", - "value": 100.0 - } - }, - { - "name": "AvoidChill", - "mod_type": "BASE", - "value": { - "kind": "literal", - "value": 100.0 - } - }, - { - "name": "AvoidIgnite", - "mod_type": "BASE", - "value": { - "kind": "literal", - "value": 100.0 - } - }, - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": 20.0 - }, - "flags": [ - "Attack" - ] - }, - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": 20.0 - }, - "flags": [ - "Cast" - ] - }, - { - "name": "MovementSpeed", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": 20.0 - } - } - ], - "conditions_set": [ - "HerEmbrace" - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 598, - "line_end": 608, - "segment_hash": "fnv1a64:7ccc1cb631cff09f" - }, - "notes": "全字面量(不吃 BuffEffectOnSelf 缩放)" - }, - { - "id": "MaddeningPresence", - "trigger_flag": "HasMaddeningPresence", - "mode_gate": "combat", - "mods": [ - { - "name": "ActionSpeed", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": -10.0 - } - }, - { - "name": "Damage", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": -10.0 - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 748, - "line_end": 751, - "segment_hash": "fnv1a64:679897be912337f4" - } - }, - { - "id": "Malediction", - "trigger_flag": "HasMalediction", - "mode_gate": "combat", - "mods": [ - { - "name": "DamageTaken", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": 10.0 - } - }, - { - "name": "Damage", - "mod_type": "INC", - "value": { - "kind": "literal", - "value": -10.0 - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 744, - "line_end": 747, - "segment_hash": "fnv1a64:56f9043453784061" - } - }, - { - "id": "Onslaught", - "trigger_flag": "Onslaught", - "mode_gate": "combat", - "effect": { - "base": 10.0, - "inc_stats": [ - "OnslaughtEffect", - "BuffEffectOnSelf" - ], - "rounding": "floor" - }, - "mods": [ - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "per_effect", - "coeff": 2.0 - }, - "flags": [ - "Attack" - ] - }, - { - "name": "Speed", - "mod_type": "INC", - "value": { - "kind": "per_effect", - "coeff": 2.0 - }, - "flags": [ - "Cast" - ] - }, - { - "name": "WarcrySpeed", - "mod_type": "INC", - "value": { - "kind": "per_effect", - "coeff": 2.0 - } - }, - { - "name": "MovementSpeed", - "mod_type": "INC", - "value": { - "kind": "per_effect", - "coeff": 1.0 - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 539, - "line_end": 570, - "segment_hash": "fnv1a64:e6f99ba533f1ffe3" - }, - "notes": "基本形(不含 Silver Flask 分支——见 OnslaughtFlask handler 条目);effect = floor(10×(1+Σinc/100))" - }, - { - "id": "OnslaughtFlask", - "trigger_flag": "Onslaught", - "mode_gate": "combat", - "handler_id": "buff:onslaught_flask", - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 539, - "line_end": 570, - "segment_hash": "fnv1a64:e6f99ba533f1ffe3" - }, - "notes": "真逻辑:Silver Flask 来源时 effect 计入 flaskData.effectInc + FlaskEffect/MagicUtilityFlaskEffect INC(依赖 M3-T4 flask merge,M3 末接通)" - }, - { - "id": "ShapersPresence", - "trigger_flag": "HasShapersPresence", - "mode_gate": "combat", - "mods": [ - { - "name": "BuffExpireFaster", - "mod_type": "MORE", - "value": { - "kind": "literal", - "value": -20.0 - } - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 752, - "line_end": 754, - "segment_hash": "fnv1a64:7b53c45448fcec95" - } - }, - { - "id": "UnholyMight", - "trigger_flag": "UnholyMight", - "mode_gate": "combat", - "effect": { - "base": 1.0, - "inc_stats": [ - "BuffEffectOnSelf" - ], - "rounding": "none" - }, - "mods": [ - { - "name": "Multiplier:UnholyMightMagnitude", - "mod_type": "BASE", - "value": { - "kind": "literal", - "value": 100.0 - } - }, - { - "name": "DamageGainAsChaos", - "mod_type": "BASE", - "value": { - "kind": "scaled_rounded", - "coeff": 0.3, - "rounding": "none" - }, - "tags": [ - { - "type": "multiplier", - "var": "UnholyMightMagnitude" - } - ] - } - ], - "verified": false, - "vendor_ref": { - "file": "Modules/CalcPerform.lua", - "line_start": 577, - "line_end": 581, - "segment_hash": "fnv1a64:6ffdd69243656a03" - }, - "notes": "vendor 注释:Magnitude 经 Multiplier 实现(perStat 数据该阶段不可用);DamageGainAsChaos = 0.3×(1+BuffEffectOnSelf/100) per Multiplier" - } - ] -} diff --git a/data/4.5.4.3/overlay/high_precision_mods.json b/data/4.5.4.3/overlay/high_precision_mods.json deleted file mode 100644 index e4e9aa78..00000000 --- a/data/4.5.4.3/overlay/high_precision_mods.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "_meta": { - "schema": "high_precision_mods/v1", - "generator": "manual transcription (M0-W4d)", - "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "2df5a7433dd2f1609e2fad8a6c3c917f923fe34f", - "source": [ - "src/Modules/Data.lua:413 (defaultHighPrecision)", - "src/Modules/Data.lua:415-530 (highPrecisionMods, 38 entries)", - "src/Classes/ModList.lua:144 (MoreInternal default round(modResult, 2))" - ], - "notes": "pobr 现状无 ScaleAddMod/MORE 精度例外消费方(audits/rearchitecture-2026-06-10/10-mod-system.md Gap 6),本表先落库零接线、零 parity 影响;more_default_round_decimals 的 pobr 准源为 pobr-core::mod_db::round_more 固定 2 位", - "audit": "版本 bump 对账(F4,drill 无自动重放通道):人工转录域——对照 vendor src/Modules/Data.lua 的 defaultHighPrecision / highPrecisionMods(38 条)与 src/Classes/ModList.lua MoreInternal 默认取整逐条复核,复核后更新 vendor_commit 与 source 行号并在 commit message 注明逐条核对结论;待 ScaleAddMod / MORE 精度例外分支落地后改走自动对账命令" - }, - "default_high_precision": 1, - "more_default_round_decimals": 2, - "mods": { - "ChaosDamageEnergyShieldLeech": { "BASE": 2 }, - "ChaosDamageLifeLeech": { "BASE": 2 }, - "ChaosDamageManaLeech": { "BASE": 2 }, - "ColdDamageEnergyShieldLeech": { "BASE": 2 }, - "ColdDamageLifeLeech": { "BASE": 2 }, - "ColdDamageManaLeech": { "BASE": 2 }, - "CritChance": { "BASE": 2 }, - "DamageEnergyShieldLeech": { "BASE": 2 }, - "DamageLifeLeech": { "BASE": 2 }, - "DamageManaLeech": { "BASE": 2 }, - "ElementalDamageEnergyShieldLeech": { "BASE": 2 }, - "ElementalDamageLifeLeech": { "BASE": 2 }, - "ElementalDamageManaLeech": { "BASE": 2 }, - "EnergyShieldDegen": { "BASE": 1 }, - "EnergyShieldDegenPercent": { "BASE": 2 }, - "EnergyShieldRegen": { "BASE": 1 }, - "EnergyShieldRegenPercent": { "BASE": 2 }, - "FireDamageEnergyShieldLeech": { "BASE": 2 }, - "FireDamageLifeLeech": { "BASE": 2 }, - "FireDamageManaLeech": { "BASE": 2 }, - "LifeDegen": { "BASE": 1 }, - "LifeDegenPercent": { "BASE": 2 }, - "LifeRegen": { "BASE": 1 }, - "LifeRegenPercent": { "BASE": 2 }, - "LightningDamageEnergyShieldLeech": { "BASE": 2 }, - "LightningDamageLifeLeech": { "BASE": 2 }, - "LightningDamageManaLeech": { "BASE": 2 }, - "ManaDegen": { "BASE": 1 }, - "ManaDegenPercent": { "BASE": 2 }, - "ManaRegen": { "BASE": 1 }, - "ManaRegenPercent": { "BASE": 2 }, - "PhysicalDamageEnergyShieldLeech": { "BASE": 2 }, - "PhysicalDamageLifeLeech": { "BASE": 2 }, - "PhysicalDamageManaLeech": { "BASE": 2 }, - "RageRegen": { "BASE": 1 }, - "ReservationMultiplier": { "MORE": 4 }, - "SelfCritChance": { "BASE": 2 }, - "SupportManaMultiplier": { "MORE": 4 } - } -} diff --git a/data/4.5.4.3/overlay/local_mods.json b/data/4.5.4.3/overlay/local_mods.json deleted file mode 100644 index a3dd14ac..00000000 --- a/data/4.5.4.3/overlay/local_mods.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_meta": { - "schema": "local_mods/v1", - "generator": "manual transcription (M0-W4d)", - "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "2df5a7433dd2f1609e2fad8a6c3c917f923fe34f", - "source": [ - "pobr 准源:crates/pobr-build/src/calc_orchestrator.rs::is_weapon_local_mod(搬迁不变式,逐值一致)", - "vendor 对照:src/Classes/Item.lua:1655-1682 calcLocal(结构化规则非文本枚举,差异仅记录不改值,见 audits/rearchitecture-2026-06-10/16-items.md)" - ], - "notes": "条目为 clean_item_text 产物口径(剥 {...} 标记 + trim + 小写);增删条目=行为变更,须独立 commit 并附 PoB2 依据", - "audit": "版本 bump 对账(F4,drill 无自动重放通道):人工转录域——条目须与 pobr 准源 crates/pobr-build/src/calc_orchestrator.rs::is_weapon_local_mod 逐值一致(搬迁不变式);vendor 升级时对照 src/Classes/Item.lua calcLocal 规则,差异仅记录不改值,复核后更新 vendor_commit 与 source 行号" - }, - "weapon": { - "increased_suffixes": [ - "% increased physical damage", - "% increased attack speed" - ], - "adds_damage_suffixes": [ - "physical damage" - ] - } -} diff --git a/data/4.5.4.3/overlay/vendor_name_aliases.json b/data/4.5.4.3/overlay/vendor_name_aliases.json deleted file mode 100644 index 2dcae192..00000000 --- a/data/4.5.4.3/overlay/vendor_name_aliases.json +++ /dev/null @@ -1,510 +0,0 @@ -{ - "_meta": { - "schema": "vendor_name_aliases/v1", - "purpose": "vendor PoB2 ModName -> PoBR canonical StatId 别名表(M6.3 切换前置数据资产)。", - "consumed_by": "NONE(纯数据资产;A 路线=引擎产物运行期翻译表 / B 路线=extract-lua 抽取期归一。owner 定夺后 D-T8 接线)。", - "generated_from": [ - "crates/pobr-core/src/mod_parser/legacy.rs::parse_name (PoBR 短语->StatId)", - "data/4.5.0.3.4/overlay/mod_parser_rules.json::name_map (vendor 短语->vendor名)" - ], - "bootstrap_method": "对齐同一触发短语:legacy 与 engine 都把短语 P 解析出单一 ModName 时,engine 名(vendor)->legacy 名(PoBR) 即一条别名。多名(聚合)短语与 damage-flag dispatch 短语不入纯别名表,登记于 structural_deferrals。", - "vendor_commit": "2df5a7433dd2f1609e2fad8a6c3c917f923fe34f", - "dualrun_report": "audits/rearchitecture-2026-06-10/blueprints/m6-dualrun-report.md", - "report": "audits/rearchitecture-2026-06-10/blueprints/m6-alias-table.md", - "coverage_note": "覆盖全部 358 name-only DIFF 涉及的 distinct stat 名(dual-run 样例 8/8 命中)。real-rename=20, identity=56, 共 76 vendor 名。" - }, - "aliases": [ - { - "vendor_name": "Accuracy", - "pobr_stat_id": "Accuracy", - "identity": true, - "via_phrase": "accuracy" - }, - { - "vendor_name": "AilmentChance", - "pobr_stat_id": "AilmentChance", - "identity": true, - "via_phrase": "chance to inflict ailments" - }, - { - "vendor_name": "AilmentThreshold", - "pobr_stat_id": "AilmentThreshold", - "identity": true, - "via_phrase": "ailment threshold" - }, - { - "vendor_name": "ArcaneSurgeEffect", - "pobr_stat_id": "ArcaneSurgeEffect", - "identity": true, - "via_phrase": "effect of arcane surge on you" - }, - { - "vendor_name": "Armour", - "pobr_stat_id": "Armour", - "identity": true, - "via_phrase": "armour" - }, - { - "vendor_name": "AvoidStun", - "pobr_stat_id": "AvoidStun", - "identity": true, - "via_phrase": "to avoid being stunned" - }, - { - "vendor_name": "BlockChance", - "pobr_stat_id": "BlockChance", - "identity": true, - "via_phrase": "to block" - }, - { - "vendor_name": "BlockChanceMax", - "pobr_stat_id": "BlockChanceMax", - "identity": true, - "via_phrase": "maximum block chance" - }, - { - "vendor_name": "ChaosDamage", - "pobr_stat_id": "ChaosDamage", - "identity": true, - "via_phrase": "chaos damage" - }, - { - "vendor_name": "ChaosDamageTaken", - "pobr_stat_id": "ChaosDamageTaken", - "identity": true, - "via_phrase": "chaos damage taken" - }, - { - "vendor_name": "ChaosResist", - "pobr_stat_id": "ChaosResistance", - "identity": false, - "via_phrase": "chaos resistance" - }, - { - "vendor_name": "ChaosResistMax", - "pobr_stat_id": "MaximumChaosResistance", - "identity": false, - "via_phrase": "maximum chaos resistance" - }, - { - "vendor_name": "ColdDamage", - "pobr_stat_id": "ColdDamage", - "identity": true, - "via_phrase": "cold damage" - }, - { - "vendor_name": "ColdDamageTaken", - "pobr_stat_id": "ColdDamageTaken", - "identity": true, - "via_phrase": "cold damage taken" - }, - { - "vendor_name": "ColdExposureEffect", - "pobr_stat_id": "ColdExposureEffect", - "identity": true, - "via_phrase": "cold exposure effect" - }, - { - "vendor_name": "ColdResist", - "pobr_stat_id": "ColdResistance", - "identity": false, - "via_phrase": "cold resistance" - }, - { - "vendor_name": "ColdResistMax", - "pobr_stat_id": "MaximumColdResistance", - "identity": false, - "via_phrase": "maximum cold resistance" - }, - { - "vendor_name": "CooldownRecovery", - "pobr_stat_id": "CooldownRecovery", - "identity": true, - "via_phrase": "cooldown recovery" - }, - { - "vendor_name": "CostEfficiency", - "pobr_stat_id": "CostEfficiency", - "identity": true, - "via_phrase": "cost efficiency" - }, - { - "vendor_name": "CritChance", - "pobr_stat_id": "CriticalStrikeChance", - "identity": false, - "via_phrase": "critical hit chance" - }, - { - "vendor_name": "CritMultiplier", - "pobr_stat_id": "CriticalStrikeMultiplier", - "identity": false, - "via_phrase": "critical damage bonus" - }, - { - "vendor_name": "DamageTaken", - "pobr_stat_id": "DamageTaken", - "identity": true, - "via_phrase": "damage taken" - }, - { - "vendor_name": "DeflectEffect", - "pobr_stat_id": "DeflectEffect", - "identity": true, - "via_phrase": "amount of damage prevented by deflection" - }, - { - "vendor_name": "DeflectionRating", - "pobr_stat_id": "DeflectionRating", - "identity": true, - "via_phrase": "deflection rating" - }, - { - "vendor_name": "Dex", - "pobr_stat_id": "Dexterity", - "identity": false, - "via_phrase": "dexterity" - }, - { - "vendor_name": "ElementalDamage", - "pobr_stat_id": "ElementalDamage", - "identity": true, - "via_phrase": "elemental damage" - }, - { - "vendor_name": "ElementalDamageTaken", - "pobr_stat_id": "ElementalDamageTaken", - "identity": true, - "via_phrase": "elemental damage taken" - }, - { - "vendor_name": "ElementalResistMax", - "pobr_stat_id": "MaximumAllElementalResistances", - "identity": false, - "via_phrase": "all maximum elemental resistances" - }, - { - "vendor_name": "EnemyBleedDuration", - "pobr_stat_id": "BleedDuration", - "identity": false, - "via_phrase": "bleed duration" - }, - { - "vendor_name": "EnemyFreezeBuildup", - "pobr_stat_id": "FreezeBuildup", - "identity": false, - "via_phrase": "freeze buildup" - }, - { - "vendor_name": "EnemyIgniteChance", - "pobr_stat_id": "EnemyIgniteChance", - "identity": true, - "via_phrase": "to ignite" - }, - { - "vendor_name": "EnemyIgniteDuration", - "pobr_stat_id": "IgniteDuration", - "identity": false, - "via_phrase": "ignite duration" - }, - { - "vendor_name": "EnemyPoisonDuration", - "pobr_stat_id": "PoisonDuration", - "identity": false, - "via_phrase": "poison duration" - }, - { - "vendor_name": "EnergyShield", - "pobr_stat_id": "EnergyShield", - "identity": true, - "via_phrase": "maximum energy shield" - }, - { - "vendor_name": "EvadeChance", - "pobr_stat_id": "EvadeChance", - "identity": true, - "via_phrase": "to evade" - }, - { - "vendor_name": "Evasion", - "pobr_stat_id": "Evasion", - "identity": true, - "via_phrase": "evasion" - }, - { - "vendor_name": "FireDamage", - "pobr_stat_id": "FireDamage", - "identity": true, - "via_phrase": "fire damage" - }, - { - "vendor_name": "FireDamageTaken", - "pobr_stat_id": "FireDamageTaken", - "identity": true, - "via_phrase": "fire damage taken" - }, - { - "vendor_name": "FireExposureEffect", - "pobr_stat_id": "FireExposureEffect", - "identity": true, - "via_phrase": "fire exposure effect" - }, - { - "vendor_name": "FireResist", - "pobr_stat_id": "FireResistance", - "identity": false, - "via_phrase": "fire resistance" - }, - { - "vendor_name": "FireResistMax", - "pobr_stat_id": "MaximumFireResistance", - "identity": false, - "via_phrase": "maximum fire resistance" - }, - { - "vendor_name": "Int", - "pobr_stat_id": "Intelligence", - "identity": false, - "via_phrase": "intelligence" - }, - { - "vendor_name": "Life", - "pobr_stat_id": "MaximumLife", - "identity": false, - "via_phrase": "maximum life" - }, - { - "vendor_name": "LifeCost", - "pobr_stat_id": "LifeCost", - "identity": true, - "via_phrase": "life cost" - }, - { - "vendor_name": "LifeCostEfficiency", - "pobr_stat_id": "LifeCostEfficiency", - "identity": true, - "via_phrase": "life cost efficiency" - }, - { - "vendor_name": "LifeRegen", - "pobr_stat_id": "LifeRegen", - "identity": true, - "via_phrase": "life regeneration rate" - }, - { - "vendor_name": "LifeReservationEfficiency", - "pobr_stat_id": "LifeReservationEfficiency", - "identity": true, - "via_phrase": "life reservation efficiency" - }, - { - "vendor_name": "LightningDamage", - "pobr_stat_id": "LightningDamage", - "identity": true, - "via_phrase": "lightning damage" - }, - { - "vendor_name": "LightningDamageTaken", - "pobr_stat_id": "LightningDamageTaken", - "identity": true, - "via_phrase": "lightning damage taken" - }, - { - "vendor_name": "LightningExposureEffect", - "pobr_stat_id": "LightningExposureEffect", - "identity": true, - "via_phrase": "lightning exposure effect" - }, - { - "vendor_name": "LightningResist", - "pobr_stat_id": "LightningResistance", - "identity": false, - "via_phrase": "lightning resistance" - }, - { - "vendor_name": "LightningResistMax", - "pobr_stat_id": "MaximumLightningResistance", - "identity": false, - "via_phrase": "maximum lightning resistance" - }, - { - "vendor_name": "LootRarity", - "pobr_stat_id": "LootRarity", - "identity": true, - "via_phrase": "rarity of items found" - }, - { - "vendor_name": "Mana", - "pobr_stat_id": "MaximumMana", - "identity": false, - "via_phrase": "maximum mana" - }, - { - "vendor_name": "ManaCost", - "pobr_stat_id": "ManaCost", - "identity": true, - "via_phrase": "mana cost" - }, - { - "vendor_name": "ManaCostEfficiency", - "pobr_stat_id": "ManaCostEfficiency", - "identity": true, - "via_phrase": "mana cost efficiency" - }, - { - "vendor_name": "ManaRegen", - "pobr_stat_id": "ManaRegen", - "identity": true, - "via_phrase": "mana regeneration rate" - }, - { - "vendor_name": "ManaReservationEfficiency", - "pobr_stat_id": "ManaReservationEfficiency", - "identity": true, - "via_phrase": "mana reservation efficiency" - }, - { - "vendor_name": "MaxLightningDamage", - "pobr_stat_id": "MaxLightningDamage", - "identity": true, - "via_phrase": "maximum lightning damage" - }, - { - "vendor_name": "MaxPhysicalDamage", - "pobr_stat_id": "MaxPhysicalDamage", - "identity": true, - "via_phrase": "maximum physical attack damage" - }, - { - "vendor_name": "MeleeEvadeChance", - "pobr_stat_id": "MeleeEvadeChance", - "identity": true, - "via_phrase": "chance to evade melee attacks" - }, - { - "vendor_name": "MinPhysicalDamage", - "pobr_stat_id": "MinPhysicalDamage", - "identity": true, - "via_phrase": "minimum physical attack damage" - }, - { - "vendor_name": "MovementSpeed", - "pobr_stat_id": "MovementSpeed", - "identity": true, - "via_phrase": "movement speed" - }, - { - "vendor_name": "PhysicalDamage", - "pobr_stat_id": "PhysicalDamage", - "identity": true, - "via_phrase": "physical damage" - }, - { - "vendor_name": "PhysicalDamageTaken", - "pobr_stat_id": "PhysicalDamageTaken", - "identity": true, - "via_phrase": "physical damage taken" - }, - { - "vendor_name": "ProjectileEvadeChance", - "pobr_stat_id": "ProjectileEvadeChance", - "identity": true, - "via_phrase": "chance to evade projectile attacks" - }, - { - "vendor_name": "ProjectileSpeed", - "pobr_stat_id": "ProjectileSpeed", - "identity": true, - "via_phrase": "projectile speed" - }, - { - "vendor_name": "ReservationEfficiency", - "pobr_stat_id": "ReservationEfficiency", - "identity": true, - "via_phrase": "reservation efficiency" - }, - { - "vendor_name": "SpellBlockChance", - "pobr_stat_id": "SpellBlockChance", - "identity": true, - "via_phrase": "spell block chance" - }, - { - "vendor_name": "SpellBlockChanceMax", - "pobr_stat_id": "SpellBlockChanceMax", - "identity": true, - "via_phrase": "maximum chance to block spell damage" - }, - { - "vendor_name": "SpellEvadeChance", - "pobr_stat_id": "SpellEvadeChance", - "identity": true, - "via_phrase": "chance to evade spells" - }, - { - "vendor_name": "Spirit", - "pobr_stat_id": "Spirit", - "identity": true, - "via_phrase": "spirit" - }, - { - "vendor_name": "SpiritReservationEfficiency", - "pobr_stat_id": "SpiritReservationEfficiency", - "identity": true, - "via_phrase": "spirit reservation efficiency" - }, - { - "vendor_name": "Str", - "pobr_stat_id": "Strength", - "identity": false, - "via_phrase": "strength" - }, - { - "vendor_name": "StunThreshold", - "pobr_stat_id": "StunThreshold", - "identity": true, - "via_phrase": "stun threshold" - }, - { - "vendor_name": "Ward", - "pobr_stat_id": "Ward", - "identity": true, - "via_phrase": "ward" - } - ], - "structural_deferrals": { - "note": "以下不入纯 name 别名表——需语义归一规则(见 m6-alias-table.md 结构四类),非单纯改名。", - "aggregate_expansion_or_flag_dispatch_phrases": [ - { - "phrase": "skill speed", - "legacy": "SkillSpeed", - "engine_names": [ - "Speed", - "WarcrySpeed", - "TotemPlacementSpeed" - ], - "class": "aggregate" - } - ], - "damage_flag_dispatch_phrases": [ - "spell damage", - "area damage", - "grenade damage", - "damage with bow", - "damage with bows", - "damage with bow skills", - "damage with crossbows", - "damage with crossbow skills", - "damage with maces", - "damage with mace", - "damage with mace skills", - "damage with spears", - "damage with spear", - "damage with spear skills", - "damage with quarterstaves", - "damage with quarterstaff", - "damage with quarterstaff skills", - "elemental damage with attacks", - "elemental damage with attack skills" - ] - } -} \ No newline at end of file diff --git a/data/4.5.0.3.4/overlay/buff_definitions.json b/data/overlay-common/buff_definitions.json similarity index 100% rename from data/4.5.0.3.4/overlay/buff_definitions.json rename to data/overlay-common/buff_definitions.json diff --git a/data/4.5.0.3.4/overlay/high_precision_mods.json b/data/overlay-common/high_precision_mods.json similarity index 100% rename from data/4.5.0.3.4/overlay/high_precision_mods.json rename to data/overlay-common/high_precision_mods.json diff --git a/data/4.5.0.3.4/overlay/local_mods.json b/data/overlay-common/local_mods.json similarity index 100% rename from data/4.5.0.3.4/overlay/local_mods.json rename to data/overlay-common/local_mods.json diff --git a/data/4.5.0.3.4/overlay/vendor_name_aliases.json b/data/overlay-common/vendor_name_aliases.json similarity index 100% rename from data/4.5.0.3.4/overlay/vendor_name_aliases.json rename to data/overlay-common/vendor_name_aliases.json From 60c4b18c5243cb9331b4e678311dd819fdfcbc29 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 11:12:06 +0800 Subject: [PATCH 14/74] chore: point bump pipeline and docs at overlay-common curated domains - regen-all.sh step 6: drop the per-version carry-forward loop; all four curated overlay domains now live in data/overlay-common/ (inherited at load). - version-bump-drill.sh 3b: resolve check-buff-refs --defs to the version overlay if present, else the overlay-common copy. - extract_parser_rules.rs: update vendor_name_aliases source-of-truth pointer. - contributing-mods.md: document the four additional common-layer domains. --- devs/scripts/version-bump-drill.sh | 3 ++ docs/contributing-mods.md | 11 +++++++- pipeline/regen-all.sh | 28 ++++++------------- .../src/extract_parser_rules.rs | 2 +- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/devs/scripts/version-bump-drill.sh b/devs/scripts/version-bump-drill.sh index 449ba33e..bd041651 100755 --- a/devs/scripts/version-bump-drill.sh +++ b/devs/scripts/version-bump-drill.sh @@ -178,7 +178,10 @@ fi # ---- [3b] 策展域对账:buff_definitions(F4,机跑通道)---- step 3b "策展域对账:check-buff-refs(buff_definitions.json)" +# buff_definitions 已迁至版本无关的 data/overlay-common/(P1-3);版本特有覆盖若存在 +# 则优先对账,否则对账 common 层基底。 DEFS="$OVERLAY_DIR/buff_definitions.json" +[[ -f "$DEFS" ]] || DEFS="$ROOT/data/overlay-common/buff_definitions.json" if [[ ! -d "$VENDOR" ]]; then echo "SKIP: vendor 检出不存在(${VENDOR})" SKIPPED+=("vendor 缺失 → buff_definitions 对账跳过") diff --git a/docs/contributing-mods.md b/docs/contributing-mods.md index cc81e600..fc5d660e 100644 --- a/docs/contributing-mods.md +++ b/docs/contributing-mods.md @@ -62,9 +62,18 @@ top of the machine-generated `.dat` import. The two files this guide is about: > it is a correction that only applies to a single game version. `regen-all.sh` > no longer carries `special_mods.json` forward between versions — the common > layer replaces that manual step (see `docs/version-bump-architecture.md` P1-3). +> +> The same version-independent common layer now also holds the other four +> hand-curated overlay domains — `buff_definitions.json`, `high_precision_mods.json`, +> `local_mods.json`, `vendor_name_aliases.json` (all in `data/overlay-common/`). +> The list domain (`buff_definitions`) merges by `id` like `special_mods`; the +> single-object domains (`high_precision_mods`, `local_mods`) are version-first +> with common fallback — a `data//overlay/.json`, if present, +> overrides the common copy entirely. Add curated edits to the common copy unless +> they are version-specific. The remaining overlay files are separate domains (base-item overrides, uniques, -gem effects, buff/curse definitions, stat descriptions, …). They follow their +gem effects, curse definitions, stat descriptions, …). They follow their own schemas in `crates/pobr-data` and are out of scope here; the same "add JSON, no Rust" philosophy applies, and `--check` will grow to cover them if they become a common contribution surface. diff --git a/pipeline/regen-all.sh b/pipeline/regen-all.sh index 2e6ff163..72ceb2b7 100755 --- a/pipeline/regen-all.sh +++ b/pipeline/regen-all.sh @@ -15,12 +15,11 @@ # pipeline/regen-all.sh # patch 读 config.json;手工域从同 patch 旧产物沿用 # OLD_PATCH=4.5.0.3.4 pipeline/regen-all.sh # 手工域从指定旧版本沿用(跨版本升级) # -# 手工策展 overlay(无 vendor/官方自动通道):buff_definitions / high_precision_mods / -# local_mods / vendor_name_aliases —— 跨版本升级时从 OLD_PATCH 沿用,并需人工复核 -# (游戏平衡变更可能使其过时)。 -# special_mods 不在此列:已迁到版本无关的 data/overlay-common/special_mods.json, -# 新版本目录由 gamedata 加载期自动合并继承,无需逐版本沿用(P1-3)。真正版本特有的 -# special_mods 修正才留在 data//overlay/special_mods.json。 +# 手工策展 overlay(无 vendor/官方自动通道)全部已迁到版本无关的 +# data/overlay-common/(special_mods / buff_definitions / high_precision_mods / +# local_mods / vendor_name_aliases):新版本目录由 gamedata 加载期自动合并/兜底 +# 继承,无需逐版本沿用(P1-3)。真正版本特有的修正才放 data//overlay/<域>.json +# (加载期按 id 覆盖 common,或对单对象域整份覆盖)。 # 韧性化:不用 -e 全局中止。前置步骤(adapter base/tree)失败仍立即退出(无 base # 数据则后续无意义);overlay 单步失败只记录、续跑其余,末尾汇总。 @@ -125,19 +124,10 @@ soft_step trigger_configs "${SYNC[@]}" gen-trigger-configs --vendor-root "$VENDO # stat_id_map(M6 E/F 段 B)须在 stat_descriptions + mod_parser_rules 之后——消费两者跑引擎派生。 soft_step stat_id_map "${SYNC[@]}" gen-stat-id-map --overlay-dir "$OVL" --out "$OVL/stat_id_map.json" -# ---- 6) 手工策展 overlay:从 OLD_PATCH 沿用(需人工复核)---- -echo "== [6/8] 手工策展 overlay 从 $OLD_PATCH 沿用" -# special_mods 已移出:策展条目在 data/overlay-common/,加载期合并继承(P1-3), -# 不再逐版本沿用。 -for f in buff_definitions high_precision_mods local_mods vendor_name_aliases; do - src="data/$OLD_PATCH/overlay/$f.json" - if [[ -f "$src" ]]; then - cp "$src" "$OVL/$f.json" - echo " carried over: $f.json(人工域,复核游戏平衡变更)" - else - echo " WARN: 缺 ${src},跳过 $f.json" >&2 - fi -done +# ---- 6) 手工策展 overlay:全部迁至 data/overlay-common/,无逐版本沿用 ---- +# special_mods / buff_definitions / high_precision_mods / local_mods / +# vendor_name_aliases 均在 data/overlay-common/,gamedata 加载期合并/兜底继承(P1-3)。 +# 版本特有修正才落 data//overlay/<域>.json。此步已无沿用动作。 # ---- 6b) 手工策展 base 文件:管线不产出,从 OLD_PATCH 沿用(需人工复核版本变更)---- # 这些是 git 跟踪、无生成器的游戏常量/定义(武器类型、game/character constants、 diff --git a/tools/sync-pob-catalog/src/extract_parser_rules.rs b/tools/sync-pob-catalog/src/extract_parser_rules.rs index 76f88769..a840435b 100644 --- a/tools/sync-pob-catalog/src/extract_parser_rules.rs +++ b/tools/sync-pob-catalog/src/extract_parser_rules.rs @@ -384,7 +384,7 @@ fn finalize_rules(doc: &mut ModParserRulesDoc) { /// M6.3 路线 B 抽取期归一:把 `name_map` 的 vendor ModName 归一为 PoBR canonical /// StatId(别名表 [`VENDOR_NAME_ALIASES`])+ 按短语展开聚合名 /// ([`AGGREGATE_EXPANSION`])。**源真理 = -/// `data/4.5.0.3.4/overlay/vendor_name_aliases.json`**(本表的 real-rename 子集 +/// `data/overlay-common/vendor_name_aliases.json`**(本表的 real-rename 子集 /// 与之一致);结构归一规格见 `blueprints/m6-alias-table.md` §3。 /// /// 设计:仅改 `names`,不增删条目(保 [`PINNED_SECTION_COUNTS`] 计数)。 From d72111330a82d38f88b3b236f41fd3892f41231d Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 11:17:45 +0800 Subject: [PATCH 15/74] docs: record pending buff_definitions vendor_ref re-review (15 drifts) --- docs/adapting-to-0.5.4b.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 3577182b..f89adfbe 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -83,6 +83,11 @@ un-ignored; `ranger-deadeye` Evasion 0.99x vs 0.5.4b golden 29774. The stale Remaining after Mageblood, re-triage against fresh `defenceModList` dumps: DeflectionRating scaling, offence per-build DPS clusters, ailment magnitude. +Also pending from the vendor bump itself: `check-buff-refs` reports 15 +`vendor_ref` line-hash drifts in `data/overlay-common/buff_definitions.json` +(e.g. OnslaughtFlask/ShapersPresence/UnholyMight) — the 4.5.4.3 upgrade swapped +the vendor without the manual buff re-review; each drifted buff needs its +vendor lines re-checked for semantic changes, then `--write` to refresh. Each is its own oracle-guided investigation; not all are single formula constants (several are unmodeled unique/flask interactions). From 6a5a08dcb4aa611e4aa48b9f9d11ceb838decc78 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 11:26:48 +0800 Subject: [PATCH 16/74] test(parity): un-ignore deflection canary after 0.5.4b re-triage Phase 0 ignored deflection_matches_golden with a ~0.60x DeflectionRating gap on monk-martial-artist-twister. Re-triage (fresh pob2-oracle run, 2026-07-17) shows the gap was entirely downstream of the Evasion gap closed by the Mageblood work (6685c30): rating derives from Evasion x EvasionGainAsDeflection% and the formula itself (CalcDefence.lua:48-54 / :1516-1522) is unchanged in 0.5.4b. Current numbers vs 4.5.4.3 golden: monk rating 22267.8 vs 22312.08 (0.998x), huntress 5664.9 vs 5666.7, warrior 0.84 exact, sorceress 0. No engine change needed; stale 0.5.0-era comment values and drifted vendor line refs (0.21.0 -> 0.22.0) refreshed alongside. --- .../tests/parity/defence_panels_golden.rs | 9 +++++++-- crates/pobr-core/src/calc/defence_panels.rs | 15 ++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/pobr-build/tests/parity/defence_panels_golden.rs b/crates/pobr-build/tests/parity/defence_panels_golden.rs index 7b4c3bb3..cd35d3f2 100644 --- a/crates/pobr-build/tests/parity/defence_panels_golden.rs +++ b/crates/pobr-build/tests/parity/defence_panels_golden.rs @@ -97,13 +97,18 @@ fn block_chance_zero_on_non_shield_builds() { /// `DeflectChance`/`DeflectionRating` @5%(13-G10):huntress/monk 系 /// `Gain Deflection Rating equal to N% of Evasion` build 是现成 fixture; /// 无 deflect 来源的 build 双值保持 0(verify 零值不误报)。 +/// +/// 0.5.4b 适配注:Phase 0 曾以「DeflectionRating ~0.60x」ignore 本 canary; +/// 复盘(oracle 复跑 2026-07-17)确认该缺口整体是 Evasion 缺口(Mageblood, +/// 6685c30)的下游——rating 派生自 `Evasion × GainAsDeflection%`,公式本身 +/// (CalcDefence.lua:48-54 / :1516-1522)与 vendor 一致,Evasion 闭合后 +/// monk rating 22267.8 vs golden 22312.08(0.998x),无需公式改动。 #[test] -#[ignore = "0.5.4b DeflectionRating 公式适配缺口(~0.60x);见 docs/adapting-to-0.5.4b.md Phase 1"] fn deflection_matches_golden() { let data = load_data(); for name in [ "huntress-spirit-walker-twister", // rating 5666.7 / chance 37 - "monk-martial-artist-twister", // rating 11229.76 / chance 58 + "monk-martial-artist-twister", // rating 22312.08 / chance 84 "warrior-titan-shield-wall", // rating 0.84 / chance 0 "sorceress-stormweaver-comet", // 0 / 0 ] { diff --git a/crates/pobr-core/src/calc/defence_panels.rs b/crates/pobr-core/src/calc/defence_panels.rs index 7b3554b4..83fa71ff 100644 --- a/crates/pobr-core/src/calc/defence_panels.rs +++ b/crates/pobr-core/src/calc/defence_panels.rs @@ -8,7 +8,8 @@ //! vendor 参照:`vendor/PathOfBuilding-PoE2/src/Modules/CalcDefence.lua` //! - Block::961-1058(BlockChanceMax 体系 + 三分型 + lucky/unlucky 幂) //! - Ward::1144-1273(per-slot 聚合 + EnergyShieldToWard) -//! - Deflection::48-54(`deflectChance` 公式)+ :1487-1506(rating 合成) +//! - Deflection::48-54(`deflectChance` 公式)+ :1516-1522(rating 合成, +//! 0.22.0 vendor 行号;0.5.4b 复核公式未变) //! - Spirit::73-126(Life/Mana/Spirit 统一池公式) use crate::{CalcConfig, ModDb}; @@ -228,7 +229,7 @@ pub fn calc_ward(db: &ModDb, cfg: &CalcConfig, es_to_ward: bool) -> f64 { } // ───────────────────────────────────────────────────────────────── -// Deflection(CalcDefence.lua:48-54 / :1487-1506) +// Deflection(CalcDefence.lua:48-54 / :1516-1522) // ───────────────────────────────────────────────────────────────── /// Deflection 面板结果。 @@ -255,14 +256,14 @@ fn deflect_chance_pct(deflection: f64, accuracy: f64, cap: f64) -> f64 { (100.0 - chance_to_not_deflect.round()).clamp(0.0, cap) } -/// Deflection 合成(CalcDefence.lua:1490-1497)。 +/// Deflection 合成(CalcDefence.lua:1516-1522)。 /// /// `DeflectionRating = ΣBASE DeflectionRating + (Evasion × ΣBASE /// EvasionGainAsDeflection/100 + Armour × ΣBASE ArmourGainAsDeflection/100) /// × calcLib.mod(DeflectionRating)`——vendor 括号语义:inc/more 乘区**只作用于 /// GainAs 派生部分**(:1490 原文)。`DeflectChance = deflectChance(rating, -/// enemyAccuracy)`;`DeflectIsLucky` → `(1−(1−p)²)`(:1492-1495); -/// `DeflectEffect = clamp(基础 40 + ΣBASE, 0, 100)`(:1496,常量 +/// enemyAccuracy)`;`DeflectIsLucky` → `(1−(1−p)²)`(:1518-1521); +/// `DeflectEffect = clamp(基础 40 + ΣBASE, 0, 100)`(:1522,常量 /// `cfg.constants.game().deflect_effect`)。 pub fn calc_deflection( db: &ModDb, @@ -294,7 +295,7 @@ pub fn calc_deflection( enemy_accuracy, cfg.constants.game().deflection_chance_cap, ); - // :1492-1495 DeflectIsLucky 幂。 + // :1518-1521 DeflectIsLucky 幂。 if db.flag(cfg, ModName::from("DeflectIsLucky")) { chance = luck_transform(chance, true, false); } @@ -383,7 +384,7 @@ pub fn fill_defence_panels(env: &mut Env, keystones: &crate::rules::DefenceKeyst // --- Ward 池(CalcDefence.lua:1144-1296;EnergyShieldToWard 走 C-1 快照)--- env.player.output.ward = calc_ward(db, cfg, keystones.energy_shield_to_ward); - // --- Deflection(CalcDefence.lua:48-54 / :1487-1506)--- + // --- Deflection(CalcDefence.lua:48-54 / :1516-1522)--- // 敌人命中读数同 Track E evade 路径(env.enemy.base.accuracy)。 let deflect = calc_deflection( db, From b29342112ca0e7e3fe1a83c889971d1ca899fbd9 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 11:27:03 +0800 Subject: [PATCH 17/74] feat(parity): admit Refraction buff EvasionGainAsDeflection (0.5.4b #2) The one genuine deflection-side gap left after re-triage: wolf-pack DeflectChance 48 vs 60 (0.80x) with Evasion already exact. Oracle defenceModList pins the missing source to Skill:SupportRefractionPlayerTwo - the Refraction I/II support's Refractive Plating buff grants EvasionGainAsDeflection BASE 20 (vendor sup_str.lua:5984/6023, support_tempered_valour_deflection_rating_%_of_evasion_rating). Implementation, following the player-buff stat-map channel: - translate_tag gains a MultiplierThreshold arm (ModStore.lua:429-459). thresholdVar form is admitted only for vars verified to have zero setters vendor-wide (RefractionMinimumValour -> threshold statically 0, matching vendor GetMultiplier-on-unset semantics); vars with real setters (AttritionCullSeconds) stay Unsupported. - player-buff ModName allowlist gains EvasionGainAsDeflection (consumer: calc_deflection, CalcDefence.lua:1516). - pob2-oracle defenceModList name set extended with the deflection family. Parity: wolf-pack DeflectChance 0.80x -> 1.00x, pathfinder 0.93x -> 1.00x; def 25-col baseline ratcheted 405 -> 407 @5% and 428 -> 429 @10% (honest re-measurement, old baselines noted in the constant comments). The same buff's ArmourAppliesToDamageTaken payloads remain reported (EHP-side lead: wolf-pack TotalEHP 0.76x -> 0.81x). --- .../pobr-build/tests/parity/ninja_parity.rs | 9 +- crates/pobr-core/src/rules/stat_map_engine.rs | 119 ++++++++++++++++++ docs/adapting-to-0.5.4b.md | 27 +++- tools/pob2-oracle/oracle.lua | 2 +- 4 files changed, 150 insertions(+), 7 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 2394c494..254b7d8b 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -618,8 +618,13 @@ const BASELINE_DEF_CORE_HIT5: usize = 138; // ItemES 后 138/144(Barrier-Life // BASELINE_DEF_CORE_HIT5 上的说明;gemling 8 列(Life/TotalEHP/5×MaxHit/LifeUnres)翻正。 // **Item ES 重算重记(+4 @5% 401→405 / +3 @10% 425→428)**:titan+stormweaver 各 // ES+ESRecoveryCap 翻正;见 BASELINE_DEF_CORE_HIT5 上说明。 -const BASELINE_DEF_HIT5: usize = 405; // ItemES 后 405/450(Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) -const BASELINE_DEF_HIT10: usize = 428; // ItemES 后 428/450(Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) +// **Refraction buff EvasionGainAsDeflection 重记(+2 @5% 405→407 / +1 @10% +// 428→429)**:support Refraction I/II 的 Refractive Plating buff 载荷 +// (`support_tempered_valour_deflection_rating_%_of_evasion_rating` BASE 20) +// 经 player buff 允收名单接入(stat_map_engine),wolf-pack DeflectChance +// 0.80x→1.00x(@5%+@10%)、pathfinder 0.93x→1.00x(@5%)。 +const BASELINE_DEF_HIT5: usize = 407; // Refraction 后 407/450(ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +const BASELINE_DEF_HIT10: usize = 429; // Refraction 后 429/450(ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 // ReservationPlayer、buff 侧 BannerPlayer(Aura)在附加位)在 buff_skill_specs diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index 56e53335..9c9a02b5 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -731,6 +731,12 @@ fn translate_player_buff_mod_name(name: &str) -> Result, Unsup "FireDamage" => Ok(vec!["FireDamage"]), "ColdDamage" => Ok(vec!["ColdDamage"]), "LightningDamage" => Ok(vec!["LightningDamage"]), + // Refraction I/II support(`sup_str.lua:5984/6023` Refractive Plating buff, + // `support_tempered_valour_deflection_rating_%_of_evasion_rating` → BASE 20): + // 消费方 = `calc::defence_panels::calc_deflection`(CalcDefence.lua:1516 + // `Evasion × ΣBASE EvasionGainAsDeflection / 100`)。同 buff 的 + // ArmourAppliesToDamageTaken 载荷暂无消费方,维持上报(EHP 侧任务)。 + "EvasionGainAsDeflection" => Ok(vec!["EvasionGainAsDeflection"]), // Sigil of Power `circle_of_power_max_stages` → 玩家 `Multiplier: // SigilOfPowerMaxStages` BASE(vendor 消费点 = GetMultiplier 动态上限 // ModStore.lua:369;PoBR 编排层把 buff 载荷中 `Multiplier:` BASE 桥进 @@ -1782,6 +1788,47 @@ pub fn translate_tag(tag: &BTreeMap) -> Result { + if !keys_subset_of(&["type", "var", "threshold", "thresholdVar", "upper"]) { + return Err(UnsupportedReason::UnsupportedTag(format!( + "MultiplierThreshold 含约定外键:{:?}", + tag.keys().collect::>() + ))); + } + let Some(var) = text("var") else { + return Err(UnsupportedReason::UnsupportedTag( + "MultiplierThreshold 缺 var".into(), + )); + }; + let threshold = match (number("threshold"), text("thresholdVar")) { + (Some(t), None) => t, + // Refraction I/II `RefractionMinimumValour`(sup_str.lua:5978-6024): + // 全 vendor 树无任何 setter → 恒 0(ValourStacks ≥ 0 恒过 gate, + // 与 vendor 默认配置行为一致)。 + (None, Some(v)) if v == "RefractionMinimumValour" => 0.0, + (None, Some(v)) => { + return Err(UnsupportedReason::UnsupportedTag(format!( + "MultiplierThreshold thresholdVar 非零 setter 未核实:{v}" + ))); + } + _ => { + return Err(UnsupportedReason::UnsupportedTag( + "MultiplierThreshold 缺 threshold".into(), + )); + } + }; + Ok(ModTag::MultiplierThreshold { + var, + threshold, + upper: matches!(tag.get("upper"), Some(StatMapValue::Bool(true))), + }) + } "PerStat" => { if !keys_subset_of(&["type", "stat", "div"]) { return Err(UnsupportedReason::UnsupportedTag(format!( @@ -3403,6 +3450,78 @@ mod tests { assert_eq!(m.effective_number(&cfg9), Some(68.0)); } + /// Refraction II 形态(vendor sup_str.lua:6023-6025:`support_tempered_ + /// valour_deflection_rating_%_of_evasion_rating` → `EvasionGainAsDeflection + /// BASE 20` + GlobalEffect Buff "Refractive Plating" + MultiplierThreshold + /// ValourStacks/thresholdVar=RefractionMinimumValour)→ 玩家侧 BASE, + /// threshold 静态折 0(该 var 全 vendor 树零 setter),默认 cfg 下生效。 + /// oracle 钉值(wolf-pack):tree 28 + 本载荷 20 = 48%,rating 11791.52。 + #[test] + fn player_buff_refraction_evasion_gain_as_deflection_maps() { + let catalog = catalog_json( + r#"{ "global": {}, "per_stat_set": { "SupportRefractionPlayerTwo": { "1": { + "support_tempered_valour_deflection_rating_%_of_evasion_rating": { + "mods": [ { "kind": "mod", "name": "EvasionGainAsDeflection", + "mod_type": "BASE", + "tags": [ { "type": "GlobalEffect", "effectType": "Buff", + "effectName": "Refractive Plating" }, + { "type": "MultiplierThreshold", "var": "ValourStacks", + "thresholdVar": "RefractionMinimumValour" } ] } ] } } } } }"#, + ); + let MappedOutcome::Mapped(items) = map_player_buff_stat( + &catalog, + "SupportRefractionPlayerTwo", + None, + "support_tempered_valour_deflection_rating_%_of_evasion_rating", + 20.0, + ) else { + panic!("期望 Mapped"); + }; + let MappedItem::Modifier(m) = &items[0] else { + panic!("期望 Modifier"); + }; + assert_eq!(m.name.as_str(), "EvasionGainAsDeflection"); + assert_eq!(m.mod_type, ModType::Base); + assert_eq!(m.value.as_number(), Some(20.0)); + assert_eq!( + m.tags, + vec![crate::ModTag::MultiplierThreshold { + var: "ValourStacks".into(), + threshold: 0.0, + upper: false, + }], + "thresholdVar 静态折 0,GlobalEffect 剥除" + ); + // 默认 cfg(ValourStacks 未注入 = 0):0 ≥ 0 → 生效,与 vendor 默认一致。 + assert!(m.matches(&crate::CalcConfig::new())); + } + + /// MultiplierThreshold thresholdVar 有 setter(Attrition + /// `AttritionCullSeconds`,act_str.lua:1258 设 Multiplier)→ 静态折 0 会 + /// 错开 gate,维持 Unsupported 整条上报。 + #[test] + fn player_buff_threshold_var_with_setter_reported() { + let catalog = catalog_json( + r#"{ "global": {}, "per_stat_set": { "AttritionPlayer": { "1": { + "attrition_cull_payload": { + "mods": [ { "kind": "mod", "name": "EvasionGainAsDeflection", + "mod_type": "BASE", + "tags": [ { "type": "GlobalEffect", "effectType": "Buff" }, + { "type": "MultiplierThreshold", "var": "EnemyPresenceSeconds", + "thresholdVar": "AttritionCullSeconds" } ] } ] } } } } }"#, + ); + assert!(matches!( + map_player_buff_stat( + &catalog, + "AttritionPlayer", + None, + "attrition_cull_payload", + 1.0, + ), + MappedOutcome::Unsupported(UnsupportedReason::UnsupportedTag(_)) + )); + } + /// Sigil of Power 最大层数载荷(`circle_of_power_max_stages` → /// `Multiplier:SigilOfPowerMaxStages` BASE,GlobalEffect 带 unscalable /// 标记——允收通过;编排层把该 BASE 桥进 cfg.multipliers 作 limitVar 分母)。 diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 3577182b..69deff7f 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -81,10 +81,29 @@ un-ignored; `ranger-deadeye` Evasion 0.99x vs 0.5.4b golden 29774. The stale `ninja-bd-deadeye.txt` embedded PlayerStats predate PoB2's Mageblood modeling (res/evasion assertions on that old sample relaxed accordingly). -Remaining after Mageblood, re-triage against fresh `defenceModList` dumps: -DeflectionRating scaling, offence per-build DPS clusters, ailment magnitude. -Each is its own oracle-guided investigation; not all are single formula -constants (several are unmodeled unique/flask interactions). +**#2 target — DeflectionRating. ✅ DONE (re-triage + Refraction buff).** +Re-triage after Mageblood showed the gap-map entry (`monk-martial-artist` +13476 vs 22312) was **downstream of the Evasion gap**: rating derives from +`Evasion × EvasionGainAsDeflection%` (CalcDefence.lua:1516-1522, formula +unchanged in 0.5.4b) and closed to 0.998x once Mageblood landed — the +deflection canary (`defence_panels_golden::deflection_matches_golden`) +re-verified against a fresh oracle run and un-ignored, no formula change. +One genuine deflection-side source remained: the **Refraction I/II support** +Refractive Plating buff (`sup_str.lua:5984/6023`, +`support_tempered_valour_deflection_rating_%_of_evasion_rating` BASE 20, +gated by `MultiplierThreshold ValourStacks/thresholdVar= +RefractionMinimumValour` — zero setters vendor-wide, so threshold is +statically 0). Admitted via the player-buff stat-map allowlist plus a +`MultiplierThreshold` arm in `translate_tag` (thresholdVar accepted only for +verified zero-setter vars). wolf-pack DeflectChance 0.80x→1.00x, pathfinder +0.93x→1.00x; def 25-col baseline 405→407 @5% / 428→429 @10%. The same buff's +`ArmourAppliesToDamageTaken` payloads stay reported (EHP-side lead: +wolf-pack TotalEHP 0.76x→0.81x, remainder likely there). + +Remaining, re-triage against fresh `defenceModList` dumps: offence per-build +DPS clusters, ailment magnitude. Each is its own oracle-guided investigation; +not all are single formula constants (several are unmodeled unique/flask +interactions). ## Tooling diff --git a/tools/pob2-oracle/oracle.lua b/tools/pob2-oracle/oracle.lua index f670769a..f591acaf 100644 --- a/tools/pob2-oracle/oracle.lua +++ b/tools/pob2-oracle/oracle.lua @@ -932,7 +932,7 @@ end local defenceModList = {} do local pdb = mainEnv.player.modDB - for _, name in ipairs({ "Armour", "Evasion", "ArmourAndEvasion", "Defences", "EnergyShield" }) do + for _, name in ipairs({ "Armour", "Evasion", "ArmourAndEvasion", "Defences", "EnergyShield", "DeflectionRating", "EvasionGainAsDeflection", "ArmourGainAsDeflection", "DeflectEffect" }) do for _, mtype in ipairs({ "BASE", "INC", "MORE" }) do pcall(function() for _, m in ipairs(pdb:Tabulate(mtype, nil, name)) do From 78459c9236c7cdfe458da49cc7f756782672a023 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 11:45:13 +0800 Subject: [PATCH 18/74] feat(parity): admit Refraction buff ArmourAppliesToDamageTaken (0.5.4b #3) The consumption chain already existed end-to-end (calc::taken:: armour_applies_pct -> build_mitigation_ctx -> per-type DamageReduction / MaximumHit / EHP, mirroring CalcDefence.lua:2361-2368), and tree-sourced percentages flow through mod_parser. The only gap was the player-buff stat-map allowlist: Refractive Plating's second stat key (support_tempered_valour_%_armour_to_apply_to_elemental_damage, sup_str.lua:6019-6021, ArmourAppliesTo{Fire,Cold,Lightning}DamageTaken BASE 30 with the same GlobalEffect + MultiplierThreshold tags as the already-admitted deflection payload) hit UnknownModName. Oracle attribution (defenceModList name set extended with the AppliesTo family) pins wolf-pack at tree 84 + buff 30 = 114% per element, EffectiveAppliedArmour = 18580 * 1.14 = 21181.2. Parity: wolf-pack Fire/Cold/LightMaxHit 0.94x -> 0.96x (three new @5% hits) and TotalEHP 0.81x -> 0.88x; no other build moved. def 25-col baseline ratcheted 407 -> 410 @5% (429 @10% unchanged; honest re-measurement, old baselines noted in the constant comments). The EHP remainder is not this channel: Armour itself 0.98x, ChaosMaxHit 0.87x (oracle ChaosEffectiveAppliedArmour = 0), Life 1.11x. --- .../pobr-build/tests/parity/ninja_parity.rs | 9 ++- crates/pobr-core/src/rules/stat_map_engine.rs | 73 ++++++++++++++++++- docs/adapting-to-0.5.4b.md | 29 +++++++- tools/pob2-oracle/oracle.lua | 2 +- 4 files changed, 105 insertions(+), 8 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 254b7d8b..ab8972d2 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -623,7 +623,14 @@ const BASELINE_DEF_CORE_HIT5: usize = 138; // ItemES 后 138/144(Barrier-Life // (`support_tempered_valour_deflection_rating_%_of_evasion_rating` BASE 20) // 经 player buff 允收名单接入(stat_map_engine),wolf-pack DeflectChance // 0.80x→1.00x(@5%+@10%)、pathfinder 0.93x→1.00x(@5%)。 -const BASELINE_DEF_HIT5: usize = 407; // Refraction 后 407/450(ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +// **Refraction buff ArmourAppliesToDamageTaken 重记(+3 @5% 407→410)**: +// 同 buff 的 `support_tempered_valour_%_armour_to_apply_to_elemental_damage` +// 载荷(三条 BASE 30)经同一 player buff 允收名单接入,消费方 +// `calc::taken::armour_applies_pct`(tree 84 + buff 30 = 114%,oracle 钉值 +// FireEffectiveAppliedArmour 21181.2)。wolf-pack Fire/Cold/LightMaxHit +// 0.94x→0.96x(@5% 翻正)、TotalEHP 0.81x→0.88x(余量 = Armour 0.98x 本体 +// 差 + ChaosMaxHit 0.87x + Life 1.11x,均与本通道无关)。@10% 无变化。 +const BASELINE_DEF_HIT5: usize = 410; // ArmourAppliesTo 后 410/450(Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) const BASELINE_DEF_HIT10: usize = 429; // Refraction 后 429/450(ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index 9c9a02b5..9e3a11b5 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -734,9 +734,17 @@ fn translate_player_buff_mod_name(name: &str) -> Result, Unsup // Refraction I/II support(`sup_str.lua:5984/6023` Refractive Plating buff, // `support_tempered_valour_deflection_rating_%_of_evasion_rating` → BASE 20): // 消费方 = `calc::defence_panels::calc_deflection`(CalcDefence.lua:1516 - // `Evasion × ΣBASE EvasionGainAsDeflection / 100`)。同 buff 的 - // ArmourAppliesToDamageTaken 载荷暂无消费方,维持上报(EHP 侧任务)。 + // `Evasion × ΣBASE EvasionGainAsDeflection / 100`)。 "EvasionGainAsDeflection" => Ok(vec!["EvasionGainAsDeflection"]), + // 同 buff 的 `support_tempered_valour_%_armour_to_apply_to_elemental_damage` + // → ArmourAppliesToDamageTaken BASE 30(Refraction II)。消费方 = + // `calc::taken::armour_applies_pct`(vendor CalcDefence.lua:2361-2368 + // `percentOfArmourApplies` → `effectiveAppliedArmour`,进 per-type + // DamageReduction / MaximumHit / EHP)。tag 形态与 deflection 载荷同 + // (GlobalEffect + MultiplierThreshold RefractionMinimumValour 静态折 0)。 + "ArmourAppliesToFireDamageTaken" => Ok(vec!["ArmourAppliesToFireDamageTaken"]), + "ArmourAppliesToColdDamageTaken" => Ok(vec!["ArmourAppliesToColdDamageTaken"]), + "ArmourAppliesToLightningDamageTaken" => Ok(vec!["ArmourAppliesToLightningDamageTaken"]), // Sigil of Power `circle_of_power_max_stages` → 玩家 `Multiplier: // SigilOfPowerMaxStages` BASE(vendor 消费点 = GetMultiplier 动态上限 // ModStore.lua:369;PoBR 编排层把 buff 载荷中 `Multiplier:` BASE 桥进 @@ -3496,6 +3504,67 @@ mod tests { assert!(m.matches(&crate::CalcConfig::new())); } + /// 同 buff 的护甲折算载荷(vendor sup_str.lua:6019-6021:`support_tempered_ + /// valour_%_armour_to_apply_to_elemental_damage` → ArmourAppliesTo{Fire,Cold, + /// Lightning}DamageTaken BASE 30,tag 形态与 deflection 载荷同)→ 三条玩家侧 + /// BASE,消费方 `calc::taken::armour_applies_pct`。oracle 钉值(wolf-pack): + /// tree 84 + 本载荷 30 = 114%,FireEffectiveAppliedArmour 21181.2。 + #[test] + fn player_buff_refraction_armour_applies_to_elements_maps() { + let catalog = catalog_json( + r#"{ "global": {}, "per_stat_set": { "SupportRefractionPlayerTwo": { "1": { + "support_tempered_valour_%_armour_to_apply_to_elemental_damage": { + "mods": [ { "kind": "mod", "name": "ArmourAppliesToFireDamageTaken", + "mod_type": "BASE", + "tags": [ { "type": "GlobalEffect", "effectType": "Buff", + "effectName": "Refractive Plating" }, + { "type": "MultiplierThreshold", "var": "ValourStacks", + "thresholdVar": "RefractionMinimumValour" } ] }, + { "kind": "mod", "name": "ArmourAppliesToColdDamageTaken", + "mod_type": "BASE", + "tags": [ { "type": "GlobalEffect", "effectType": "Buff", + "effectName": "Refractive Plating" }, + { "type": "MultiplierThreshold", "var": "ValourStacks", + "thresholdVar": "RefractionMinimumValour" } ] }, + { "kind": "mod", "name": "ArmourAppliesToLightningDamageTaken", + "mod_type": "BASE", + "tags": [ { "type": "GlobalEffect", "effectType": "Buff", + "effectName": "Refractive Plating" }, + { "type": "MultiplierThreshold", "var": "ValourStacks", + "thresholdVar": "RefractionMinimumValour" } ] } ] } } } } }"#, + ); + let MappedOutcome::Mapped(items) = map_player_buff_stat( + &catalog, + "SupportRefractionPlayerTwo", + None, + "support_tempered_valour_%_armour_to_apply_to_elemental_damage", + 30.0, + ) else { + panic!("期望 Mapped"); + }; + let names: Vec<&str> = items + .iter() + .map(|item| { + let MappedItem::Modifier(m) = item else { + panic!("期望 Modifier"); + }; + assert_eq!(m.mod_type, ModType::Base); + assert_eq!(m.value.as_number(), Some(30.0)); + // 默认 cfg(ValourStacks 未注入 = 0):0 ≥ 0 → 生效。 + assert!(m.matches(&crate::CalcConfig::new())); + m.name.as_str() + }) + .collect(); + assert_eq!( + names, + vec![ + "ArmourAppliesToFireDamageTaken", + "ArmourAppliesToColdDamageTaken", + "ArmourAppliesToLightningDamageTaken", + ] + ); + } + /// MultiplierThreshold thresholdVar 有 setter(Attrition /// `AttritionCullSeconds`,act_str.lua:1258 设 Multiplier)→ 静态折 0 会 /// 错开 gate,维持 Unsupported 整条上报。 diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 3e4178ea..bbfb0e44 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -100,11 +100,32 @@ verified zero-setter vars). wolf-pack DeflectChance 0.80x→1.00x, pathfinder `ArmourAppliesToDamageTaken` payloads stay reported (EHP-side lead: wolf-pack TotalEHP 0.76x→0.81x, remainder likely there). +**#3 target — `ArmourAppliesToDamageTaken` (Refraction buff payload). +✅ DONE.** The consumption chain already existed end-to-end +(`calc::taken::armour_applies_pct` → `build_mitigation_ctx` → per-type +DamageReduction / MaximumHit / EHP, mirroring CalcDefence.lua:2361-2368 +`percentOfArmourApplies` → `effectiveAppliedArmour`); tree-sourced percentages +flow through `mod_parser` ("X% of Armour also applies to Y damage taken"). +The only gap was the player-buff stat-map allowlist: the Refractive Plating +buff's second stat key +(`support_tempered_valour_%_armour_to_apply_to_elemental_damage`, +`sup_str.lua:6019-6021`, three `ArmourAppliesTo{Fire,Cold,Lightning} +DamageTaken` BASE 30 mods with the same GlobalEffect + MultiplierThreshold +tags as the deflection payload) hit `UnknownModName`. Oracle attribution +(extended `defenceModList` name set) pins wolf-pack at tree 84 + buff 30 = +114% per element, `EffectiveAppliedArmour` = 18580 × 1.14 = 21181.2. +Admitting the three names closes wolf-pack Fire/Cold/LightMaxHit +0.94x→0.96x (@5% hits) and TotalEHP 0.81x→0.88x; def 25-col baseline +407→410 @5% (429 @10% unchanged). The EHP remainder is **not** this channel: +Armour itself 0.98x (18169.78 vs 18580), ChaosMaxHit 0.87x (oracle +`ChaosEffectiveAppliedArmour` = 0 — chaos gap is elsewhere), and Life 1.11x +(PoBR overestimate 2973.6 vs 2674). + Remaining, re-triage against fresh `defenceModList` dumps: offence per-build -DPS clusters, ailment magnitude, and the wolf-pack EHP remainder -(`ArmourAppliesToDamageTaken`, see #2 above). Each is its own oracle-guided -investigation; not all are single formula constants (several are unmodeled -unique/flask interactions). +DPS clusters, ailment magnitude, and the wolf-pack EHP remainder decomposed +above (Armour 0.98x / ChaosMaxHit 0.87x / Life 1.11x). Each is its own +oracle-guided investigation; not all are single formula constants (several +are unmodeled unique/flask interactions). Also pending from the vendor bump itself: `check-buff-refs` reports 15 `vendor_ref` line-hash drifts in `data/overlay-common/buff_definitions.json` (e.g. OnslaughtFlask/ShapersPresence/UnholyMight) — the 4.5.4.3 upgrade swapped diff --git a/tools/pob2-oracle/oracle.lua b/tools/pob2-oracle/oracle.lua index f591acaf..54ba3856 100644 --- a/tools/pob2-oracle/oracle.lua +++ b/tools/pob2-oracle/oracle.lua @@ -932,7 +932,7 @@ end local defenceModList = {} do local pdb = mainEnv.player.modDB - for _, name in ipairs({ "Armour", "Evasion", "ArmourAndEvasion", "Defences", "EnergyShield", "DeflectionRating", "EvasionGainAsDeflection", "ArmourGainAsDeflection", "DeflectEffect" }) do + for _, name in ipairs({ "Armour", "Evasion", "ArmourAndEvasion", "Defences", "EnergyShield", "DeflectionRating", "EvasionGainAsDeflection", "ArmourGainAsDeflection", "DeflectEffect", "ArmourAppliesToFireDamageTaken", "ArmourAppliesToColdDamageTaken", "ArmourAppliesToLightningDamageTaken", "ArmourAppliesToChaosDamageTaken", "ArmourAppliesToElementalDamageTaken", "EvasionAppliesToFireDamageTaken", "EvasionAppliesToColdDamageTaken", "EvasionAppliesToLightningDamageTaken", "EnergyShieldAppliesToFireDamageTaken", "EnergyShieldAppliesToColdDamageTaken", "EnergyShieldAppliesToLightningDamageTaken" }) do for _, mtype in ipairs({ "BASE", "INC", "MORE" }) do pcall(function() for _, m in ipairs(pdb:Tabulate(mtype, nil, name)) do From 331ca3a80c6abdbf5bc182da3cf9ffe241bb99f1 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 12:28:11 +0800 Subject: [PATCH 19/74] feat(parity): Atziri's Communion spirit-to-life reservation + auto LowLife condition (0.5.4b #4a) 0.5.4b added the LifeReservePercentPerSpirit mechanic (vendor CalcDefence.lua:248-254): Atziri's Communion converts a supported persistent skill's Spirit reservation into a Life percent reservation (constant stat skill_reserves_X_life_permyriad_per_spirit_instead_of_spirit = 66 -> 0.66%/spirit). Heavily-reserved builds then auto-enter Low Life (CalcDefence.lua:335-350: unreserved/max <= LowPoolThreshold 0.35 -> condList["LowLife"]), unlocking the 'while on Low Life' mod family (tree nodes + Direstrike support buff, whose stat-map entry already carried Condition:LowLife but the condition was never set). Three consumption-point fixes, all generic: - xml_build: gems serialized with only nameSpec (lineage supports like Atziri's Communion carry no skillId/gemId) are no longer dropped; GemSkillRef gains name_spec and stage_build_view resolves it against granted_effects by normalized name (PoB2 SkillsTab nameSpec lookup). - spirit_reservation_modifiers: when a group support carries the permyriad stat, the skill's spirit flat (incl. ExtraSpirit) converts to a LifeReservedPercent INC mod (Life-pool inc/more/efficiency names, vendor round-2), spirit reservation zeroed. Eternal Rage: 155 x 0.66 x 0.9 = 92.07% -> LifeReserved 270 (golden 270). - CalculationSession::bridge_low_pool_conditions (orchestrator stage 6e): mirrors the vendor low-pool loop for Life; explicit config conditionLowLife stays authoritative. Oracle evidence (huntress-ritualist-bow-shot, 0.5.4b): damageModList shows +60 (Tree:56453) and +70 (Skill:SupportDirestrikePlayerTwo) attack Damage INC gated on Condition:LowLife = exactly the missing 130 INC (PoBR pool scale 3.48 vs oracle 4.78 across all damage types); mainOutput LifeUnreserved 23 / SpiritUnreserved 10 vs PoBR's old 293 / -130. Parity: ritualist TotalDPS 0.68x->0.99x, TotalDotDPS 0.60x->1.00x, SpiritUnres/LifeUnres exact; abyssal-lich (also wears Communion) TotalDPS 0.62x->0.83x. Aggregates below re-ratcheted in #4b commit. --- apps/pobr-wasm/src/build_api/analysis.rs | 1 + apps/pobr-wasm/src/build_api/request.rs | 1 + crates/pobr-build/src/build.rs | 8 +++ .../pobr-build/src/calc_orchestrator/buffs.rs | 66 +++++++++++++++++++ .../src/calc_orchestrator/granted_skills.rs | 1 + .../pobr-build/src/calc_orchestrator/mod.rs | 56 ++++++++++++++++ crates/pobr-build/src/xml_build.rs | 28 +++++--- crates/pobr-core/src/calc/session.rs | 41 ++++++++++++ 8 files changed, 193 insertions(+), 9 deletions(-) diff --git a/apps/pobr-wasm/src/build_api/analysis.rs b/apps/pobr-wasm/src/build_api/analysis.rs index fb1cf71a..260cd18c 100644 --- a/apps/pobr-wasm/src/build_api/analysis.rs +++ b/apps/pobr-wasm/src/build_api/analysis.rs @@ -248,6 +248,7 @@ fn apply_variant( gem_level: gem.level, quality: gem.quality, stat_set_index: None, + name_spec: None, }); if let Some(effect) = data.gem_effects.get(&gem.skill_id) { group.gem_ids.push(effect.gem_id.clone()); diff --git a/apps/pobr-wasm/src/build_api/request.rs b/apps/pobr-wasm/src/build_api/request.rs index b3de7e7f..ee61a6c2 100644 --- a/apps/pobr-wasm/src/build_api/request.rs +++ b/apps/pobr-wasm/src/build_api/request.rs @@ -192,6 +192,7 @@ fn socket_group_from_input(input: &SocketGroupInput, data: &BuildData) -> Socket gem_level: gem.level, quality: gem.quality, stat_set_index: None, + name_spec: None, }); if let Some(gem_id) = gem_id { group.gem_ids.push(gem_id); diff --git a/crates/pobr-build/src/build.rs b/crates/pobr-build/src/build.rs index 3c8e219b..e2606fdb 100644 --- a/crates/pobr-build/src/build.rs +++ b/crates/pobr-build/src/build.rs @@ -37,6 +37,12 @@ pub struct GemSkillRef { /// `"nil"`,解析归一化为 `None`)。`statSetIndexCalcs`(calcs 页独立选择) /// M1 不做,解析忽略。 pub stat_set_index: Option, + /// PoB `` 显示名——仅当 XML 缺 `skillId`/`gemId`(lineage + /// support 如 Atziri's Communion 的序列化形态)时携带,`skill_id` 此时为空串。 + /// 编排层 `stage_build_view` 按显示名归一匹配 granted_effects 回填 + /// `skill_id`(PoB2 SkillsTab 按 nameSpec 反查 gem 的等价物);未解析成功 + /// 的引用在全部消费点因 `granted_effects.get("")` 落空而惰性跳过。 + pub name_spec: Option, } /// 一组同插槽的技能宝石(主动技能 + 其辅助)。简化等价物:用稳定 gem id 表示。 @@ -128,6 +134,7 @@ impl SocketGroup { gem_level, quality, stat_set_index: None, + name_spec: None, }); self } @@ -144,6 +151,7 @@ impl SocketGroup { gem_level, quality: 0, stat_set_index: Some(stat_set_index), + name_spec: None, }); self } diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index c628f6b3..bb01d01f 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -495,6 +495,12 @@ pub(crate) fn spirit_reservation_modifiers( let own = level_row(data, &gem.skill_id, gem.gem_level); let mut flat = own.and_then(|r| r.spirit_reservation_flat).unwrap_or(0.0); let mut mult = 1.0 + own.and_then(|r| r.reservation_multiplier).unwrap_or(0.0) / 100.0; + // Spirit→Life 保留转换(vendor CalcDefence.lua:248-254,0.5.4b 新增; + // Atziri's Communion support 的 constant stat + // `skill_reserves_X_life_permyriad_per_spirit_instead_of_spirit` = 66, + // SkillStatMap div=100 → 每点 Spirit 保留 0.66% Life)。命中时该技能的 + // Spirit 保留整体转为 Life 百分比保留(Spirit 置 0)。 + let mut spirit_to_life = 0.0; // 同组 support:spirit flat(ExtraSpirit)+ reservation_multiplier MORE。 for sup in &group.gem_skills { if data @@ -508,6 +514,14 @@ pub(crate) fn spirit_reservation_modifiers( flat += row.spirit_reservation_flat.unwrap_or(0.0); mult *= 1.0 + row.reservation_multiplier.unwrap_or(0.0) / 100.0; } + spirit_to_life += data + .effect_stats(&sup.skill_id, sup.gem_level, sup.quality, sup.stat_set_index) + .all() + .filter(|s| { + s.stat == "skill_reserves_X_life_permyriad_per_spirit_instead_of_spirit" + }) + .map(|s| s.value / 100.0) + .sum::(); } // Blasphemy per-curse 预留(vendor CalcDefence.lua:273-284):`IsBlasphemy` // 效果按**被包 curse 数**各加 `blasphemy_base_spirit_reservation_per_socketed_curse` @@ -574,6 +588,58 @@ pub(crate) fn spirit_reservation_modifiers( ); // PoB2 对保留倍率乘积截断到 4 位小数后再乘 base(floor(x, 4))。 let mult = (mult * 10000.0).floor() / 10000.0; + // Spirit→Life 转换分支(vendor CalcDefence.lua:248-254 + per-pool 循环 + // name="Life"):Life.basePercent = Spirit.baseFlat × 每点转换率; + // 因子换用 Life 池名(LifeReserved/Reserved、LifeReservationEfficiency/ + // ReservationEfficiency;宝石品质效率项 pool 无关照除);百分比 + // round 2 位(vendor :312)。产出 `LifeReservedPercent` INC 由 + // perform 预留段消费(ritualist:Eternal Rage 155×0.66×0.9 = 92.07% + // → LifeReserved 270 / LifeUnreserved 23,golden 一致)。 + if spirit_to_life > 0.0 { + let life_reserved_names = [ + pobr_data::prelude::ModName::from("LifeReserved"), + pobr_data::prelude::ModName::from("Reserved"), + ]; + let l_inc = db.sum( + pobr_data::prelude::ModType::Inc, + &gem_cfg, + &life_reserved_names, + ); + let l_more = db.more(&gem_cfg, &life_reserved_names); + let l_factor = if l_more > 0.0 && l_inc > -100.0 { + (100.0 + l_inc) / 100.0 * l_more + } else { + 0.0 + }; + let l_eff_names = [ + pobr_data::prelude::ModName::from("LifeReservationEfficiency"), + pobr_data::prelude::ModName::from("ReservationEfficiency"), + ]; + let l_eff = (quality_eff + + db.sum(pobr_data::prelude::ModType::Inc, &gem_cfg, &l_eff_names)) + .max(-100.0); + let l_eff_more = db.more(&gem_cfg, &l_eff_names); + let percent = (flat * spirit_to_life * mult * l_factor / (1.0 + l_eff / 100.0) + / l_eff_more + * 100.0) + .round() + / 100.0; + if percent > 0.0 { + let origin = ModifierSource::new(SourceId::new( + SourceKind::SkillGem, + format!("spirit.{}", gem.skill_id), + )) + .with_raw_text(format!( + "life reservation from spirit {} ({} × {spirit_to_life}%)", + gem.skill_id, flat + )); + mods.push( + Modifier::number("LifeReservedPercent", ModType::Inc, percent) + .with_origin(origin), + ); + } + continue; + } let mut reserved = (flat * mult * res_factor / (1.0 + efficiency / 100.0) / eff_more) .round() .max(0.0); diff --git a/crates/pobr-build/src/calc_orchestrator/granted_skills.rs b/crates/pobr-build/src/calc_orchestrator/granted_skills.rs index 33a73f9c..b829af3b 100644 --- a/crates/pobr-build/src/calc_orchestrator/granted_skills.rs +++ b/crates/pobr-build/src/calc_orchestrator/granted_skills.rs @@ -149,6 +149,7 @@ pub(crate) fn augment_item_granted_skills(build: &Build, data: &BuildData) -> Op gem_level: level, quality: 0, stat_set_index: None, + name_spec: None, }], main_active_skill: None, }); diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index 0d6722d3..e92fcbd2 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -478,6 +478,10 @@ pub fn calculate_with_data_session( // 6d. 来源授予的条件 flag → cfg 条件桥接(Bonded modifiers / Arcane Surge)。 inject_condition_bridges(&mut session); + // 6e. 低生命自动条件(vendor CalcDefence.lua:335-350:未预留比例 ≤ 0.35 → + // Condition:LowLife)。须在预留 mod 注入(4d)与池值可算(6c)之后。 + session.bridge_low_pool_conditions(); + // 诊断 dump(POBR_DBG_UNSUPPORTED / ALLMODS / STAT,parity 排查用)。 stage_debug_dumps(&session); @@ -592,9 +596,61 @@ fn stage_build_view<'a>(build: &'a Build, data: &BuildData) -> Cow<'a, Build> { build = Cow::Owned(adjusted); } + // nameSpec-only gem 引用 → skill_id 回填(PoB2 SkillsTab 按 nameSpec 反查 + // gem 的等价物):lineage support(如 Atziri's Communion)在 XML 里缺 + // skillId/gemId,仅有显示名。按归一化名匹配 granted_effects id;未命中 + // 保持空 id(全部消费点惰性跳过)。 + if let Some(resolved) = resolve_name_spec_gems(&build, data) { + build = Cow::Owned(resolved); + } + build } +/// 把 `GemSkillRef { skill_id: "", name_spec: Some(name) }` 的显示名解析为授予 +/// 效果 id。归一化 = 小写 + 仅保留字母数字;候选 id 剥 `Player` 后缀(含 +/// `PlayerTwo/Three` 等 lineage 变体不匹配也无妨——它们的 XML 带 skillId), +/// support id 另剥 `Support` 前缀(`SupportAtzirisCommunionPlayer` → +/// `atziriscommunion` = nameSpec "Atziri's Communion" 归一形)。无改动返回 None。 +fn resolve_name_spec_gems(build: &Build, data: &BuildData) -> Option { + fn norm(s: &str) -> String { + s.chars() + .filter(char::is_ascii_alphanumeric) + .map(|c| c.to_ascii_lowercase()) + .collect() + } + let pending: Vec = build + .socket_groups + .iter() + .flat_map(|g| &g.gem_skills) + .filter(|gem| gem.skill_id.is_empty()) + .filter_map(|gem| gem.name_spec.clone()) + .collect(); + if pending.is_empty() { + return None; + } + let mut lookup: std::collections::HashMap = std::collections::HashMap::new(); + for id in data.granted_effects.keys() { + let stem = id.strip_suffix("Player").unwrap_or(id); + let stem = stem.strip_prefix("Support").unwrap_or(stem); + lookup.insert(norm(stem), id.as_str()); + } + let mut out = build.clone(); + let mut changed = false; + for group in &mut out.socket_groups { + for gem in &mut group.gem_skills { + if gem.skill_id.is_empty() + && let Some(name) = &gem.name_spec + && let Some(id) = lookup.get(&norm(name)) + { + gem.skill_id = (*id).to_string(); + changed = true; + } + } + } + changed.then_some(out) +} + /// 阶段 1:主技能解析——分等级参数、终态 skillTypes 不动点(vendor /// CalcActiveSkill.lua:179-214)、伤害 flag / 判别位 / 伤害关键词。必须最先 /// 运行:行动速率要进 base_input(stage_weapon_bases)、类型 flag / 战斗条件 diff --git a/crates/pobr-build/src/xml_build.rs b/crates/pobr-build/src/xml_build.rs index b2f15fca..f5629a26 100644 --- a/crates/pobr-build/src/xml_build.rs +++ b/crates/pobr-build/src/xml_build.rs @@ -1145,15 +1145,17 @@ fn parse_socket_groups(xml: &str) -> Result, XmlError> { "Gem" if in_target_set => { if let Some(cur) = current.as_mut() && attr_bool_default_true(&e, b"enabled") - && let Some(gem_id) = attr_value(&e, b"gemId") - && !gem_id.is_empty() { + let gem_id = attr_value(&e, b"gemId").filter(|v| !v.is_empty()); + let skill_id = attr_value(&e, b"skillId").filter(|v| !v.is_empty()); + // lineage support(如 Atziri's Communion)序列化时缺 + // skillId/gemId,仅有 nameSpec——保留显示名,交编排层 + // `stage_build_view` 按 granted_effects 反查回填 id。 + let name_spec = attr_value(&e, b"nameSpec").filter(|v| !v.is_empty()); // 捕获每个启用 gem 的 skillId + level + quality(active 与 // support 皆收)。quality 属性缺失/非法按 0(无品质),对齐 // PoB2 SkillsTab.lua 的 `quality` 属性读取(缺省 0)。 - if let Some(skill_id) = attr_value(&e, b"skillId") - && !skill_id.is_empty() - { + if skill_id.is_some() || name_spec.is_some() { let level = attr_value(&e, b"level") .and_then(|v| v.parse::().ok()) .unwrap_or(1); @@ -1166,20 +1168,28 @@ fn parse_socket_groups(xml: &str) -> Result, XmlError> { // (calcs 页独立选择)M1 不做,忽略。 let stat_set_index = attr_value(&e, b"statSetIndex") .and_then(|v| v.parse::().ok()); - // 组内首个启用 gem 视为主动技能(PoB Gem 列表 active 在前)。 - if cur.active_skill_id.is_none() { + // 组内首个带 skillId 的启用 gem 视为主动技能 + // (PoB Gem 列表 active 在前;nameSpec-only 引用 + // 均为 lineage support,不参与主动技能判定)。 + if let Some(skill_id) = &skill_id + && cur.active_skill_id.is_none() + { cur.active_skill_id = Some(skill_id.clone()); cur.active_gem_level = Some(level); cur.active_gem_quality = Some(quality); } + let name_spec_pending = skill_id.is_none(); cur.gem_skills.push(crate::build::GemSkillRef { - skill_id, + skill_id: skill_id.unwrap_or_default(), gem_level: level, quality, stat_set_index, + name_spec: if name_spec_pending { name_spec } else { None }, }); } - cur.gem_ids.push(gem_id); + if let Some(gem_id) = gem_id { + cur.gem_ids.push(gem_id); + } } } "StatSetIndex" if in_target_set => { diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index 4938b570..851b7e46 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -164,6 +164,47 @@ impl CalculationSession { .flag(&self.env.cfg, ModName::from(name)) } + /// 低生命自动条件桥(vendor CalcDefence.lua:335-350:`(max − reserved)/max ≤ + /// LowPoolThreshold(0.35)` → `condList["LowLife"] = true`)。0.5.4b 起 + /// Spirit→Life 保留转换(Atziri's Communion)使重保留 build 自动进入 Low Life, + /// 解锁「while on Low Life」族词条(tree + Direstrike 支援 buff)。 + /// + /// 须在全部来源注入后、[`perform_minimal`](Self::perform_minimal) 之前调用 + /// (预留 mod 已注入,条件在 perform 的聚合查询期生效——vendor 同序: + /// doActorLifeManaSpiritReservation 先于 calcs.offence)。预留聚合口径与 + /// perform 预留段逐式相同(ReservationMultiplier floor4 + efficiency 除数)。 + /// 显式 config 条件(`conditionLowLife`)优先,不被覆盖。 + // ponytail: 只桥 Life(唯一有 fixture 佐证的池);LowMana/LowSpirit 同一 + // vendor 循环,出现消费 build 时按本函数样板扩展。LowLifePercentage 覆盖 + // stat(vendor :337)全 fixture 无来源,未建模。 + pub fn bridge_low_pool_conditions(&mut self) { + if self.env.cfg.conditions.contains_key("LowLife") { + return; + } + let life = self.pool_total("MaximumLife"); + if life <= 0.0 { + return; + } + let db = &self.env.player.mod_db; + let cfg = &self.env.cfg; + let mult = (db.more(cfg, &[ModName::from("ReservationMultiplier")]) * 10_000.0).floor() + / 10_000.0; + let eff_names = [ + ModName::from("LifeReservationEfficiency"), + ModName::from("ReservationEfficiency"), + ]; + let eff_inc = db.sum(ModType::Inc, cfg, &eff_names).max(-100.0); + let divisor = ((1.0 + eff_inc / 100.0) * db.more(cfg, &eff_names)).max(1e-12); + let factor = mult / divisor; + let flat = db.sum(ModType::Base, cfg, &[ModName::from("LifeReserved")]) * factor; + let percent = db.sum(ModType::Inc, cfg, &[ModName::from("LifeReservedPercent")]) * factor; + let reserved = super::survivability::reservation(life, flat, percent).reserved; + if (life - reserved) / life <= self.env.cfg.constants.game_constants.game.low_pool_threshold + { + self.env.cfg.conditions.insert("LowLife".into(), true); + } + } + pub fn add_modifier_texts( &mut self, texts: impl IntoIterator>, From 0ff4239a8d0c364066b87dc8740bba96400d6ed3 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 12:28:27 +0800 Subject: [PATCH 20/74] feat(parity): Voices 'Allocates N Sinister Jewel sockets' admits sinister-socket jewels (0.5.4b #4b) 0.5.4b introduced the Voices unique jewel ('Allocates 2 Sinister Jewel sockets'; vendor ModParser.lua 0.22.0 delta: GrantedPassive SinisterJewelSockets, consumed by PassiveSpec.lua:1067-1090 which allocates the first N sinister sockets in voices_jewel_slot1..5 alias order). PoBR's tree-jewel gate only admitted jewels whose socket node is in the allocated set, so jewels sitting in the never-tree-allocated sinister sockets were silently dropped. parse_items_and_slots now scans allocated-socket jewels for the allocates-N line and treats the first N sinister socket node ids (pinned from vendor TreeData/0_5/tree.lua sinister=true + aliasPassiveSocket: 62152, 26178, 23960, 39087, 3367) as allocated for the jewel filter. Older tree versions have no source for this mod, so behavior there is unchanged. Oracle evidence: ritualist critModList includes Item:18 Phoenix Splinter (+18 INC CritMultiplier) and Item:23 Oblivion Sliver (+13 INC CritChance, +19 INC CritMultiplier) socketed at 62152/26178 - exactly PoBR's missing 13 INC chance / 37 INC multiplier (27.839->29.1 chance, 4.37->4.74 mult, both now exact). Titan and abyssal-lich also wear Voices and pick up their sinister jewels. --- crates/pobr-build/src/xml_build.rs | 37 ++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/pobr-build/src/xml_build.rs b/crates/pobr-build/src/xml_build.rs index f5629a26..5f84704c 100644 --- a/crates/pobr-build/src/xml_build.rs +++ b/crates/pobr-build/src/xml_build.rs @@ -662,11 +662,31 @@ fn parse_items_and_slots( // 树上珠宝在 ``(非 ItemSet),单独收集; // 仅保留 socket 节点已分配的珠宝。 + let socket_items = parse_socket_node_items(xml)?; + // Voices(0.5.4b unique):「Allocates N Sinister Jewel sockets」——已分配 socket + // 内珠宝带此词条时,按 vendor alias 序把前 N 个 sinister socket 视为已分配 + // (vendor PassiveSpec.lua:1067-1090 `voices_jewel_slot1..5` → 0_5 tree 节点 id, + // 钉自 TreeData/0_5/tree.lua `sinister=true` + `aliasPassiveSocket`)。 + // ponytail: 节点 id 钉 0_5 树(sinister socket 仅存在于 0.5.4+;旧树版本无此 + // 词条来源,零行为)。树版本再迭代时 parity 门禁会点名此列,届时改从树数据取。 + const SINISTER_SOCKETS_0_5: [u32; 5] = [62152, 26178, 23960, 39087, 3367]; + let sinister_count: usize = socket_items + .iter() + .filter(|(node, _)| allocated.contains(node)) + .filter_map(|(_, id)| items.get(id)) + .flat_map(|it| it.implicit_texts.iter().chain(&it.modifier_texts)) + .filter_map(|t| sinister_socket_alloc_count(t)) + .sum(); + let sinister_allocated: std::collections::HashSet = SINISTER_SOCKETS_0_5 + .iter() + .copied() + .take(sinister_count) + .collect(); let mut all_jewel_ids = jewel_ids; all_jewel_ids.extend( - parse_socket_node_items(xml)? + socket_items .into_iter() - .filter(|(node, _)| allocated.contains(node)) + .filter(|(node, _)| allocated.contains(node) || sinister_allocated.contains(node)) .map(|(_, item)| item), ); all_jewel_ids.sort_unstable(); @@ -683,6 +703,19 @@ fn parse_items_and_slots( Ok((out, jewels, flask_charms, use_second_weapon_set)) } +/// 解析「Allocates N Sinister Jewel socket(s)」词条 → N(vendor ModParser.lua +/// `allocates (%d+) sinister jewel sockets?` → GrantedPassive SinisterJewelSockets)。 +/// 非此词条返回 None。 +fn sinister_socket_alloc_count(text: &str) -> Option { + let rest = text.trim().strip_prefix("Allocates ")?; + let (num, tail) = rest.split_once(' ')?; + matches!( + tail.trim().to_ascii_lowercase().as_str(), + "sinister jewel sockets" | "sinister jewel socket" + ) + .then(|| num.parse().ok())? +} + /// 解析树插槽 `` → `(socket_node, item_id)`(itemId≠0)。 fn parse_socket_node_items(xml: &str) -> Result, XmlError> { let mut reader = Reader::from_str(xml); From ae3c517f7d894a5dc8145374a31267b66c1a7255 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 12:30:54 +0800 Subject: [PATCH 21/74] test(parity): ratchet baselines for 0.5.4b #4 + un-ignore ember DPS canary Communion/LowLife + Voices sinister sockets land honest gains, recorded per ratchet discipline (old -> new, all measured): - offensive @5% 46 -> 52, @10% 55 -> 59 - dot @5% 11 -> 13, @10% 13 -> 18 - defensive 25-col @5% 410 -> 413, @10% 429 -> 434 - defensive core-8 @5% 138 -> 140 - panel offence @5% 27 -> 38, @10% 30 -> 39 canary_fire_spell_armour (druid-oracle-ember-fusillade) re-verified at TotalDPS 1.00x (548742 vs golden 549423) - the gap-map ~0.60x entry had already been closed by earlier fixes without the canary being revisited; un-ignored. --- .../pobr-build/tests/parity/golden_canary.rs | 5 ++- .../pobr-build/tests/parity/ninja_parity.rs | 41 +++++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/crates/pobr-build/tests/parity/golden_canary.rs b/crates/pobr-build/tests/parity/golden_canary.rs index a769ba3a..31861203 100644 --- a/crates/pobr-build/tests/parity/golden_canary.rs +++ b/crates/pobr-build/tests/parity/golden_canary.rs @@ -142,8 +142,11 @@ fn canary_evasion_melee() { } /// 火焰法术 / 护甲层(CI,Life=1 跳过)。 +/// +/// Phase 0 曾以「0.5.4b offence DPS 适配缺口(~0.60x)」`#[ignore]`;#4 重验 +/// ember-fusillade TotalDPS 已 1.00x(548742 vs 549423——gap map 条目在先前 +/// mana-multiplier / Arcane Surge 等修复中顺带闭合,未及回验),un-ignore。 #[test] -#[ignore = "0.5.4b offence DPS 适配缺口(~0.60x);见 docs/adapting-to-0.5.4b.md Phase 1"] fn canary_fire_spell_armour() { let d = load_data(); let n = "druid-oracle-ember-fusillade"; diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index ab8972d2..5b301a6a 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -528,7 +528,12 @@ fn compute_tallies(verbose: bool) -> (Tally, Tally, Tally, Tally, Vec) { // 展示行」改为 PoB2 口径「基底 DB 重算 (esBase+flat)×(1+localInc/100)×(1+quality/100)」 // (Item.lua:1994-1996;展示行跨数据版本会滞后)——titan ES 41→55、stormweaver ES // 986→1120,各连带 ESRecoveryCap。见 calc_orchestrator/defence.rs::item_rolled_defence。 -const BASELINE_DEF_CORE_HIT5: usize = 138; // ItemES 后 138/144(Barrier-Life 136;Mageblood 135;迁移基线 118;0.5.0=139) +// **0.5.4b #4 Communion/LowLife + Voices 重记(+2 @5% core-8 138→140)**: +// huntress-ritualist SpiritUnres −13.00x→1.00x / LifeUnres 12.74x→1.01x—— +// Atziri's Communion 的 Spirit→Life 保留转换(LifeReservePercentPerSpirit, +// vendor CalcDefence.lua:248-254)接入后双列翻正;abyssal-lich(同戴 Communion) +// SpiritUnres inf→1.00x 同根。见 buffs.rs spirit_reservation_modifiers 转换分支。 +const BASELINE_DEF_CORE_HIT5: usize = 140; // Communion 后 140/144(ItemES 138;Barrier-Life 136;Mageblood 135;迁移基线 118;0.5.0=139) // **per-socket-filled 修复重记(+1 @5%/@10%)**:gemling-legionnaire 身甲 Morior Invictus // `+14 to Spirit per Socket filled`(×5 socket)经 `RunesSocketedIn{SlotName}` Multiplier // 接入 → Spirit 180→250(0.72x→1.00x,翻正)。详见 collect.rs::filter_parseable 闸门 + @@ -630,8 +635,11 @@ const BASELINE_DEF_CORE_HIT5: usize = 138; // ItemES 后 138/144(Barrier-Life // FireEffectiveAppliedArmour 21181.2)。wolf-pack Fire/Cold/LightMaxHit // 0.94x→0.96x(@5% 翻正)、TotalEHP 0.81x→0.88x(余量 = Armour 0.98x 本体 // 差 + ChaosMaxHit 0.87x + Life 1.11x,均与本通道无关)。@10% 无变化。 -const BASELINE_DEF_HIT5: usize = 410; // ArmourAppliesTo 后 410/450(Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) -const BASELINE_DEF_HIT10: usize = 429; // Refraction 后 429/450(ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) +// **0.5.4b #4 Communion/LowLife + Voices 重记(+3 @5% 410→413 / +5 @10% 429→434)**: +// core-8 的 SpiritUnres/LifeUnres 翻正(见 BASELINE_DEF_CORE_HIT5 上说明)+ +// abyssal-lich EnergyShield 0.93x→0.98x(Voices sinister 珠宝的 ES 词条找回)。 +const BASELINE_DEF_HIT5: usize = 413; // Communion+Voices 后 413/450(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 // ReservationPlayer、buff 侧 BannerPlayer(Aura)在附加位)在 buff_skill_specs @@ -677,8 +685,19 @@ const BASELINE_DEF_HIT10: usize = 429; // Refraction 后 429/450(ItemES 428; // mana_pool 并回填 cfg.stats["Mana"],但漏刷 cfg.multipliers["Mana"](per-100-max-Mana // 类词条如 Arcane Intensity 读它)→ 池转换 build 的 mana 缩放用了转换前的旧值。补刷后 // blood-mage SpellDamage INC 39→105、TotalDPS 0.74x→0.87x,其 DoT 基底随之抬升。见 perform.rs。 -const BASELINE_OFF_HIT5: usize = 46; // mana-mult 后 46/80(Silver-speed 45;迁移基线 39;0.5.0=71) -const BASELINE_OFF_HIT10: usize = 55; // mana-mult 后 55/80(Silver-speed 54;迁移基线 47;0.5.0=74) +// **0.5.4b #4 Communion/LowLife + Voices 重记(off +6 @5% 46→52 / +4 @10% 55→59)**: +// 两个 0.5.4b 新机制在真实 build 上叠加成 per-build DPS 簇(gap map「~0.60x」项): +// 1. Atziri's Communion Spirit→Life 保留转换(vendor CalcDefence.lua:248-254)→ +// 重保留 build 自动 Low Life(:335-350 unreserved ≤ 35%)→「while on Low Life」 +// 族增伤解锁(ritualist:tree +60 与 Direstrike buff +70 attack INC,oracle +// damageModList 钉值 = 缺口 130 INC 整)。 +// 2. Voices「Allocates 2 Sinister Jewel sockets」→ sinister socket 珠宝入计 +// (ritualist crit chance/mult 13/37 INC 找回,双列精确闭合)。 +// huntress-ritualist TotalDPS 0.68x→0.99x(AvgDamage/CritChance/CritMultiplier 同翻)、 +// witch-abyssal-lich TotalDPS 0.62x→0.91x(Speed 0.91x→1.00x、CritChance 0.98x、 +// CritMultiplier 0.75x→0.97x,其中 Speed/CritChance @5% 翻正、DPS 未回带)。 +const BASELINE_OFF_HIT5: usize = 52; // Communion+Voices 后 52/80(mana-mult 46;Silver-speed 45;迁移基线 39;0.5.0=71) +const BASELINE_OFF_HIT10: usize = 59; // Communion+Voices 后 59/80(mana-mult 55;Silver-speed 54;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -739,8 +758,12 @@ const BASELINE_OFF_HIT10: usize = 55; // mana-mult 后 55/80(Silver-speed 54 // 18 build 逐格 diff 仅 blood-mage 三格变动。冻结榜只剩 legacy `split`。 // **bloodmage mana-mult 重记(dot +2 @5% 9→11 / @10% 11→13)**:见 BASELINE_OFF_HIT5 上 // 说明——mana 缩放抬 blood-mage 的 spell DoT 基底,其 TotalDotDPS/CombinedDPS 翻正。 -const BASELINE_DOT_HIT5: usize = 11; // mana-mult 后 11/37(迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 13; // mana-mult 后 13/37(迁移基线 11;0.5.0=28) +// **0.5.4b #4 Communion/LowLife + Voices 重记(dot +2 @5% 11→13 / +5 @10% 13→18)**: +// ritualist TotalDotDPS 0.60x→1.00x / CombinedDPS 0.63x→0.99x(LowLife 增伤 + +// sinister 珠宝的 damaging-ailment-magnitude 词条同时抬 bleed/poison 基底); +// abyssal-lich dot 列随 hit 侧收敛入 @10% 带。 +const BASELINE_DOT_HIT5: usize = 13; // Communion+Voices 后 13/37(mana-mult 11;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 18; // Communion+Voices 后 18/37(mana-mult 13;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 @@ -758,8 +781,8 @@ const BASELINE_DOT_HIT10: usize = 13; // mana-mult 后 13/37(迁移基线 11 /// (template.rs / special_mod.rs)同 commit 全量化——一批 `ModTag::SkillTypes` /// 域词条(Area/Projectile/Grenade 等)在 panel 口径开始正确匹配。effective /// 主口径与防御/进攻/dot 主基线逐值持平(纯 panel 侧收敛)。 -const PANEL_OFF_HIT5: usize = 27; // 4.5.4.3 迁移基线 27/80(0.5.0=44) -const PANEL_OFF_HIT10: usize = 30; // 4.5.4.3 迁移基线 30/80(0.5.0=46) +const PANEL_OFF_HIT5: usize = 38; // 0.5.4b #4 Communion+Voices 后 38/80(迁移基线 27;0.5.0=44) +const PANEL_OFF_HIT10: usize = 39; // 0.5.4b #4 Communion+Voices 后 39/80(迁移基线 30;0.5.0=46) /// 回归门禁:聚合命中数不得低于已记录基线([`BASELINE_*`])。CI gate,防止改动倒退 parity。 #[test] From c2503ac698ea74384773574e9a2cb09f61a11e94 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 12:52:17 +0800 Subject: [PATCH 22/74] feat(oracle): ORACLE_EXTRA_STATS ad-hoc per-source stat tabulation ORACLE_EXTRA_STATS="CooldownRecovery,Speed" dumps the per-source mod list for arbitrary vendor stat names (same shape as critModList) under extraModList. Zero cost when unset. Used to pin the deadeye grenade Speed gap to three tree nodes (21077/354/48429, 15% CDR each, SkillType 159-gated) without hand-bisecting the build XML. --- tools/pob2-oracle/oracle.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tools/pob2-oracle/oracle.lua b/tools/pob2-oracle/oracle.lua index 54ba3856..d8d72c0f 100644 --- a/tools/pob2-oracle/oracle.lua +++ b/tools/pob2-oracle/oracle.lua @@ -709,6 +709,23 @@ for _, name in ipairs({ "CritMultiplier", "CritChance" }) do end end +-- Ad-hoc stat tabulation: ORACLE_EXTRA_STATS="CooldownRecovery,Speed" dumps the +-- per-source mod list for any vendor stat names (same shape as critModList), +-- keyed under extraModList. Zero cost when the env var is unset. +local extraModList +do + local names = os.getenv("ORACLE_EXTRA_STATS") + if names and #names > 0 then + extraModList = { INC = {}, MORE = {}, BASE = {}, OVERRIDE = {} } + for name in names:gmatch("[^,%s]+") do + for _, mt in ipairs({ "BASE", "INC", "MORE", "OVERRIDE" }) do + local list = tabulateModList(mt, name) + if #list > 0 then extraModList[mt][name] = list end + end + end + end +end + -- Aggregate cross-check: Sum/More over the same names PoBR uses, so the JSON -- carries the authoritative PoB2 totals alongside the per-mod breakdown. local damageAgg = { @@ -990,6 +1007,7 @@ local report = { intermediates = intermediates, damageModList = damageModList, critModList = critModList, + extraModList = extraModList, damageAgg = damageAgg, components = components, summedBase = summedBase, From 7c8f5cd5d2fb994212955d0c08fbcdf49dc1bb1c Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 12:52:17 +0800 Subject: [PATCH 23/74] feat(parser): grenade phrases are live SkillType tags again in 0.5.4b (0.5.4b #4c) vendor 0.22.0 added 'not grantedEffect.fromItem' to the ModParser gem- name registration loop (ModParser.lua:6423, in the 0.21->0.22 delta): MeleeGrenadeLauncherPlayer (name "Grenade", fromItem) no longer registers in skillNameList, so the modFlagList phrases 'grenade' and 'for grenade skills' are live SkillType.Grenade tags again. PoBR's extractor still carried the 0.21-era runtime facts from PR#53 (VENDOR_DEAD_FLAG_PHRASES dropped 'for grenade skills'; VENDOR_SKILLNAME_PREEMPTED rewrote 'grenade' to an inert SkillName tag), so grenade-scoped mods silently under-applied. Evidence: - run-parsemod (0.5.4b vendor): '15% increased Cooldown Recovery Rate for Grenade Skills' -> CooldownRecovery INC 15 + SkillType 159, empty leftover; '12% increased Grenade Damage' likewise. - oracle extraModList: deadeye is missing exactly Tree:21077/354/48429 (3 x 15 CDR, SkillType-gated) -> INC -18 vs vendor +27, cooldown 6.098s vs 3.937s (Speed 0.164 vs golden 0.254). Changes: empty both extractor override tables (kept with a dated note and the re-audit method), regen mod_parser_rules.json (exactly the two grenade entries change) + parsed_mods.json + test_pins snapshot. Parity: deadeye Speed 0.65x->1.00x, TotalDPS 0.53x->0.83x (remaining gap is the pre-existing per-hit shortfall documented in pob2_parity.rs); gemling Speed/AvgDamage/TotalDPS/CombinedDPS all 1.00-1.04x. Ratchets: off @5% 52->56, @10% 59->62; dot 13->14 / 18->19; panel 38->40 / 39->41. The stale embedded deadeye sample (pre-0.5.4b export) no longer gets a TotalDPS assertion - its Speed calibration is invalidated by the CDR rebalance; AverageDamage tightened 0.55->0.20 (measured 1.04x). The authoritative DPS gate is the ninja_parity 0.5.4b fixture golden. --- .../pobr-build/tests/parity/ninja_parity.rs | 23 +++++++++---- crates/pobr-build/tests/parity/pob2_parity.rs | 11 ++++-- data/4.5.4.3/generated/parsed_mods.json | 10 +++--- data/4.5.4.3/generated/test_pins.json | 2 +- data/4.5.4.3/overlay/mod_parser_rules.json | 17 +++++++--- .../src/extract_parser_rules.rs | 34 +++++++++---------- 6 files changed, 62 insertions(+), 35 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 5b301a6a..17883164 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -696,8 +696,17 @@ const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refract // huntress-ritualist TotalDPS 0.68x→0.99x(AvgDamage/CritChance/CritMultiplier 同翻)、 // witch-abyssal-lich TotalDPS 0.62x→0.91x(Speed 0.91x→1.00x、CritChance 0.98x、 // CritMultiplier 0.75x→0.97x,其中 Speed/CritChance @5% 翻正、DPS 未回带)。 -const BASELINE_OFF_HIT5: usize = 52; // Communion+Voices 后 52/80(mana-mult 46;Silver-speed 45;迁移基线 39;0.5.0=71) -const BASELINE_OFF_HIT10: usize = 59; // Communion+Voices 后 59/80(mana-mult 55;Silver-speed 54;迁移基线 47;0.5.0=74) +// **0.5.4b #4 grenade 短语解禁重记(off +4 @5% 52→56 / +3 @10% 59→62)**:vendor +// 0.22.0 ModParser gem 名注册循环新增 `not grantedEffect.fromItem` 排除 +// (ModParser.lua:6423)——`MeleeGrenadeLauncherPlayer`(name "Grenade",fromItem) +// 不再抢注 skillNameList,`grenade` / `for grenade skills` 短语恢复为 live +// `SkillType.Grenade` tag(run-parsemod 双证)。PR#53 时代(0.21 语义)的抽取侧 +// 死条目/惰性改写撤销 + mod_parser_rules/parsed_mods 再生成。deadeye 3×15 CDR +// 树词条(oracle extraModList 钉源 Tree:21077/354/48429)生效:deadeye Speed +// 0.65x→1.00x(翻正)、TotalDPS 0.53x→0.83x;gemling Speed/AvgDamage/TotalDPS/ +// CombinedDPS 全部 1.00-1.04x 翻正。 +const BASELINE_OFF_HIT5: usize = 56; // grenade 短语解禁后 56/80(Communion+Voices 52;mana-mult 46;迁移基线 39;0.5.0=71) +const BASELINE_OFF_HIT10: usize = 62; // grenade 短语解禁后 62/80(Communion+Voices 59;mana-mult 55;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -762,8 +771,10 @@ const BASELINE_OFF_HIT10: usize = 59; // Communion+Voices 后 59/80(mana-mult // ritualist TotalDotDPS 0.60x→1.00x / CombinedDPS 0.63x→0.99x(LowLife 增伤 + // sinister 珠宝的 damaging-ailment-magnitude 词条同时抬 bleed/poison 基底); // abyssal-lich dot 列随 hit 侧收敛入 @10% 带。 -const BASELINE_DOT_HIT5: usize = 13; // Communion+Voices 后 13/37(mana-mult 11;迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 18; // Communion+Voices 后 18/37(mana-mult 13;迁移基线 11;0.5.0=28) +// **0.5.4b #4 grenade 短语解禁重记(dot +1 @5% 13→14 / +1 @10% 18→19)**: +// gemling CombinedDPS 0.65x→1.04x 翻正(见 BASELINE_OFF_HIT5 上说明)。 +const BASELINE_DOT_HIT5: usize = 14; // grenade 短语解禁后 14/37(Communion+Voices 13;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 19; // grenade 短语解禁后 19/37(Communion+Voices 18;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 @@ -781,8 +792,8 @@ const BASELINE_DOT_HIT10: usize = 18; // Communion+Voices 后 18/37(mana-mult /// (template.rs / special_mod.rs)同 commit 全量化——一批 `ModTag::SkillTypes` /// 域词条(Area/Projectile/Grenade 等)在 panel 口径开始正确匹配。effective /// 主口径与防御/进攻/dot 主基线逐值持平(纯 panel 侧收敛)。 -const PANEL_OFF_HIT5: usize = 38; // 0.5.4b #4 Communion+Voices 后 38/80(迁移基线 27;0.5.0=44) -const PANEL_OFF_HIT10: usize = 39; // 0.5.4b #4 Communion+Voices 后 39/80(迁移基线 30;0.5.0=46) +const PANEL_OFF_HIT5: usize = 40; // 0.5.4b #4(Communion+Voices 38;grenade 短语解禁 +2);迁移基线 27;0.5.0=44 +const PANEL_OFF_HIT10: usize = 41; // 0.5.4b #4(Communion+Voices 39;grenade 短语解禁 +2);迁移基线 30;0.5.0=46 /// 回归门禁:聚合命中数不得低于已记录基线([`BASELINE_*`])。CI gate,防止改动倒退 parity。 #[test] diff --git a/crates/pobr-build/tests/parity/pob2_parity.rs b/crates/pobr-build/tests/parity/pob2_parity.rs index 708f59a9..a56deb7e 100644 --- a/crates/pobr-build/tests/parity/pob2_parity.rs +++ b/crates/pobr-build/tests/parity/pob2_parity.rs @@ -160,8 +160,15 @@ fn deadeye_parity_report() { // 两层「过算抵消欠算」**叠乘**拆除(0.613x × 0.647/0.817 ≈ 0.485x,实测吻合), // 容差按合并后真实偏差放宽,只防进一步倒退;待 grenade 冷却吞吐 / Mirage 数据 // 补齐后收紧。 - assert_within(&pob2, "AverageDamage", out.total_hit_avg, 0.55); - assert_within(&pob2, "TotalDPS", out.dps, 0.55); + // + // 0.5.4b #4(grenade 短语解禁)后收口:AvgDamage 实测 1.04x——上述「过算抵消 + // 欠算」链中 base 侧缺口已被历次修复闭合,容差 0.55→0.20 收紧。TotalDPS **不再 + // 对本旧样本断言**(与上方 res/evasion 同理):内嵌 PlayerStat 导出早于 0.5.4b + // 的 grenade CDR 再平衡(vendor 0.22.0 ModParser gem 循环加 `fromItem` 排除, + // `for grenade skills` 短语复活 → 3×15 CDR 树词条生效,Speed 0.164→0.254), + // 旧样本 Speed 口径已失效;权威 DPS 门禁走 ninja_parity 的 0.5.4b fixture + // golden(Speed 0.254 精确、TotalDPS 0.83x 且棘轮守护)。 + assert_within(&pob2, "AverageDamage", out.total_hit_avg, 0.20); // Evasion 见上方注释:内嵌样本缺 Mageblood(14301),不硬断言;权威值走 fixture golden。 } diff --git a/data/4.5.4.3/generated/parsed_mods.json b/data/4.5.4.3/generated/parsed_mods.json index ace80545..af6d5cab 100644 --- a/data/4.5.4.3/generated/parsed_mods.json +++ b/data/4.5.4.3/generated/parsed_mods.json @@ -8325,7 +8325,7 @@ "type": "Inc", "value": 10.0, "tags": [ - "SkillName { names: [\"grenade\"] }" + "SkillTypes(SkillTypes([0, 0, 1073741824, 0, 0]))" ] } ] @@ -10452,7 +10452,7 @@ "type": "Inc", "value": 12.0, "tags": [ - "SkillName { names: [\"grenade\"] }" + "SkillTypes(SkillTypes([0, 0, 1073741824, 0, 0]))" ] } ] @@ -11404,7 +11404,7 @@ "type": "Inc", "value": 15.0, "tags": [ - "SkillName { names: [\"grenade\"] }" + "SkillTypes(SkillTypes([0, 0, 1073741824, 0, 0]))" ] } ] @@ -23564,7 +23564,7 @@ "type": "Inc", "value": 40.0, "tags": [ - "SkillName { names: [\"grenade\"] }" + "SkillTypes(SkillTypes([0, 0, 1073741824, 0, 0]))" ] } ] @@ -30055,7 +30055,7 @@ "type": "Inc", "value": -80.0, "tags": [ - "SkillName { names: [\"grenade\"] }" + "SkillTypes(SkillTypes([0, 0, 1073741824, 0, 0]))" ] } ] diff --git a/data/4.5.4.3/generated/test_pins.json b/data/4.5.4.3/generated/test_pins.json index 76527434..a1ef8be5 100644 --- a/data/4.5.4.3/generated/test_pins.json +++ b/data/4.5.4.3/generated/test_pins.json @@ -14,7 +14,7 @@ "damage_types": 5, "degen_types": 32, "distinct_forms": 29, - "flag_phrases": 205, + "flag_phrases": 206, "flag_types": 26, "forms": 95, "name_map": 789, diff --git a/data/4.5.4.3/overlay/mod_parser_rules.json b/data/4.5.4.3/overlay/mod_parser_rules.json index 85897fc2..282f4397 100644 --- a/data/4.5.4.3/overlay/mod_parser_rules.json +++ b/data/4.5.4.3/overlay/mod_parser_rules.json @@ -3,8 +3,8 @@ "schema": "mod_parser_rules/v1", "generator": "sync-pob-catalog extract-lua", "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "29ab8262dd4a867eac56165afb8391a509e34a0e", - "vendor_commit_subject": "29ab826 Add support for Darkness Enthroned and Atziri's Splendour augment mods (#2343)", + "vendor_commit": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", + "vendor_commit_subject": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", "extracted_files": [ "Modules/ModParser.lua" ], @@ -6212,6 +6212,15 @@ "Curse" ] }, + { + "phrase": "for grenade skills", + "tags": [ + { + "type": "SkillType", + "skill_type": "Grenade" + } + ] + }, { "phrase": "for hexes", "tags": [ @@ -6429,8 +6438,8 @@ "phrase": "grenade", "tags": [ { - "type": "SkillName", - "skillName": "Grenade" + "type": "SkillType", + "skill_type": "Grenade" } ] }, diff --git a/tools/sync-pob-catalog/src/extract_parser_rules.rs b/tools/sync-pob-catalog/src/extract_parser_rules.rs index a840435b..97500b78 100644 --- a/tools/sync-pob-catalog/src/extract_parser_rules.rs +++ b/tools/sync-pob-catalog/src/extract_parser_rules.rs @@ -126,23 +126,23 @@ const POBR_EXTRA_TAG_PHRASES: &[(&str, &str)] = &[ /// vendor 死条目(B3 裁决):modFlagList 里存在、但 vendor `parseMod` 运行时**永不 /// 命中**的 flag 短语——skillNameList 的 SkillName 剥离(order=1,在 flag scan -/// 之前)抢先吃掉短语中的技能名(`for grenade skills` 中 `grenade` 被剥成 -/// SkillName tag,残留 `forSkills` → 整行 extra 非空 → PoB2 树加载丢弃该行)。 -/// 抽取表保留这些条目会让 engine 比 vendor「多生效」词条(deadeye/gemling 实测 -/// +45% CDR over-apply)。裁决方法:`tools/pob2-oracle/run-parsemod.sh` 实喂词条 -/// 看 leftover;版本升级后若 parity 出现同型 over-apply,用同一方法审计新条目。 -const VENDOR_DEAD_FLAG_PHRASES: &[&str] = &["for grenade skills"]; - -/// vendor skillNameList 抢先条目(B3 死条目机制的**改写**变体):modFlagList 的裸 -/// `grenade` 短语在 vendor 运行时永不产 SkillType tag——skillNameList(order=1) -/// 先把词剥成 `SkillName{"Grenade"}`(技能名单里存在字面名 "Grenade" 的条目), -/// 残留恰好可解析 → 整行保留但 tag 恒不匹配(无真实技能叫 "grenade")= 零效果。 -/// ModCache golden 逐字对齐:`12% increased Grenade Damage` → `Damage INC 12 + -/// SkillName{"Grenade"}`;oracle 探针(塞词条到 gemling 戒指)实测 ratio=1.0000。 -/// 抽取表保留 SkillType payload 会让 engine 比 vendor 多生效(gemling/deadeye -/// 全类型 inc +60 over-apply,AvgDmg 1.13x 根因)。与死条目的区别:这里 vendor -/// 行为是「保留行 + 惰性 tag」而非丢行,故改写 payload 而非剔除条目。 -const VENDOR_SKILLNAME_PREEMPTED_FLAG_PHRASES: &[(&str, &str)] = &[("grenade", "Grenade")]; +/// 之前)抢先吃掉短语中的技能名,残留非空 → 整行不生效。裁决方法: +/// `tools/pob2-oracle/run-parsemod.sh` 实喂词条看 leftover;版本升级后 parity +/// 出现同型 over/under-apply 时用同一方法重审。 +/// +/// **0.5.4b(vendor 0.22.0)清空 grenade 项**:vendor gem 名注册循环新增 +/// `not grantedEffect.fromItem` 排除(ModParser.lua:6423,0.21→0.22 delta), +/// `MeleeGrenadeLauncherPlayer`(name "Grenade",fromItem)不再注册 +/// skillNameList → `grenade` / `for grenade skills` 恢复为 modFlagList 的 +/// **live** `SkillType.Grenade` tag(run-parsemod 实证:`15% increased +/// Cooldown Recovery Rate for Grenade Skills` → CooldownRecovery INC 15 + +/// SkillType 159,leftover 空)。0.21 时代的死条目/抢先改写(PR#53)随之撤销; +/// deadeye 3×15 CDR 树词条 under-apply(Speed 0.164 vs oracle 0.254)即此根因。 +const VENDOR_DEAD_FLAG_PHRASES: &[&str] = &[]; + +/// vendor skillNameList 抢先条目(机制说明见 [`VENDOR_DEAD_FLAG_PHRASES`]—— +/// 「保留行 + 惰性 SkillName tag」改写变体)。0.5.4b 起清空(同上 fromItem 排除)。 +const VENDOR_SKILLNAME_PREEMPTED_FLAG_PHRASES: &[(&str, &str)] = &[]; /// 完整 overlay 文档(生成侧;消费侧 schema = /// [`pobr_data::catalog::parser_rules::ModParserRulesDoc`],serde 形状一致)。 From 4119e81902aa1f192595e71774e4263b9663c465 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 12:55:02 +0800 Subject: [PATCH 24/74] docs: record 0.5.4b #4 offence DPS clusters in adapting-to-0.5.4b + fmt Adds the #4 section (three sub-fixes: Communion/LowLife chain, Voices sinister sockets, grenade phrase un-preemption), the honest aggregate deltas, and the decomposed remaining offence clusters (attack AverageDamage family, titan bifurcated-crit, frost-bomb pre-existing cooldown gap, ritualist low-life EHP calibre note). Includes cargo fmt fixups on the #4a code. --- .../pobr-build/src/calc_orchestrator/buffs.rs | 17 +++-- crates/pobr-core/src/calc/session.rs | 13 +++- docs/adapting-to-0.5.4b.md | 70 +++++++++++++++++-- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index bb01d01f..c8747e3a 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -515,7 +515,12 @@ pub(crate) fn spirit_reservation_modifiers( mult *= 1.0 + row.reservation_multiplier.unwrap_or(0.0) / 100.0; } spirit_to_life += data - .effect_stats(&sup.skill_id, sup.gem_level, sup.quality, sup.stat_set_index) + .effect_stats( + &sup.skill_id, + sup.gem_level, + sup.quality, + sup.stat_set_index, + ) .all() .filter(|s| { s.stat == "skill_reserves_X_life_permyriad_per_spirit_instead_of_spirit" @@ -619,11 +624,11 @@ pub(crate) fn spirit_reservation_modifiers( + db.sum(pobr_data::prelude::ModType::Inc, &gem_cfg, &l_eff_names)) .max(-100.0); let l_eff_more = db.more(&gem_cfg, &l_eff_names); - let percent = (flat * spirit_to_life * mult * l_factor / (1.0 + l_eff / 100.0) - / l_eff_more - * 100.0) - .round() - / 100.0; + let percent = + (flat * spirit_to_life * mult * l_factor / (1.0 + l_eff / 100.0) / l_eff_more + * 100.0) + .round() + / 100.0; if percent > 0.0 { let origin = ModifierSource::new(SourceId::new( SourceKind::SkillGem, diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index 851b7e46..fdde004d 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -187,8 +187,8 @@ impl CalculationSession { } let db = &self.env.player.mod_db; let cfg = &self.env.cfg; - let mult = (db.more(cfg, &[ModName::from("ReservationMultiplier")]) * 10_000.0).floor() - / 10_000.0; + let mult = + (db.more(cfg, &[ModName::from("ReservationMultiplier")]) * 10_000.0).floor() / 10_000.0; let eff_names = [ ModName::from("LifeReservationEfficiency"), ModName::from("ReservationEfficiency"), @@ -199,7 +199,14 @@ impl CalculationSession { let flat = db.sum(ModType::Base, cfg, &[ModName::from("LifeReserved")]) * factor; let percent = db.sum(ModType::Inc, cfg, &[ModName::from("LifeReservedPercent")]) * factor; let reserved = super::survivability::reservation(life, flat, percent).reserved; - if (life - reserved) / life <= self.env.cfg.constants.game_constants.game.low_pool_threshold + if (life - reserved) / life + <= self + .env + .cfg + .constants + .game_constants + .game + .low_pool_threshold { self.env.cfg.conditions.insert("LowLife".into(), true); } diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index bbfb0e44..2ae82dcd 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -121,11 +121,71 @@ Armour itself 0.98x (18169.78 vs 18580), ChaosMaxHit 0.87x (oracle `ChaosEffectiveAppliedArmour` = 0 — chaos gap is elsewhere), and Life 1.11x (PoBR overestimate 2973.6 vs 2674). -Remaining, re-triage against fresh `defenceModList` dumps: offence per-build -DPS clusters, ailment magnitude, and the wolf-pack EHP remainder decomposed -above (Armour 0.98x / ChaosMaxHit 0.87x / Life 1.11x). Each is its own -oracle-guided investigation; not all are single formula constants (several -are unmodeled unique/flask interactions). +**#4 target — offence per-build DPS clusters. ✅ DONE (three sub-fixes).** +Re-triage first: the gap-map poster child `druid-oracle-ember-fusillade` was +already at TotalDPS 1.00x (closed as a side effect of earlier fixes; its +`canary_fire_spell_armour` re-verified and un-ignored). The real 0.5.4b +clusters (cells where PoBR ≈ old 0.5.0 golden while the 0.5.4b golden moved): + +- **#4a Atziri's Communion → auto Low Life** (`huntress-ritualist-bow-shot`, + `witch-abyssal-lich-detonate-dead`): 0.5.4b added + `LifeReservePercentPerSpirit` (CalcDefence.lua:248-254) — the Communion + support converts a persistent skill's Spirit reservation into Life percent + reservation (0.66%/spirit), pushing heavy-reservation builds under the + auto Low Life threshold (:335-350, unreserved ≤ 35%), which unlocks the + "while on Low Life" mod family. Oracle `damageModList` pinned ritualist's + missing 130 Damage INC to exactly the two LowLife-gated entries (tree + +60, Direstrike buff +70 — the buff's stat-map entry already carried + `Condition:LowLife`; only the condition was never true). Three generic + consumption points: nameSpec-only gem resolution (lineage supports + serialize without skillId/gemId), the spirit→life conversion branch in + `spirit_reservation_modifiers`, and + `CalculationSession::bridge_low_pool_conditions` (orchestrator 6e). + Ritualist TotalDPS 0.68x→0.99x, TotalDotDPS 0.60x→1.00x, + SpiritUnres/LifeUnres exact. +- **#4b Voices sinister jewel sockets** (ritualist, titan, abyssal-lich): + the 0.5.4b Voices unique "Allocates 2 Sinister Jewel sockets" + (ModParser 0.22.0 `GrantedPassive SinisterJewelSockets`, + PassiveSpec.lua:1067-1090 alias order `voices_jewel_slot1..5` → 0_5 tree + nodes 62152/26178/23960/39087/3367). PoBR's tree-jewel gate dropped + jewels in never-allocated sinister sockets; `parse_items_and_slots` now + admits the first N pinned sockets. Ritualist CritChance 27.84→29.10 and + CritMultiplier 4.37→4.74, both exact (missing 13/37 INC matched the two + sinister jewels line-for-line). +- **#4c grenade phrases un-preempted** (`ranger-deadeye-` / + `mercenary-gemling-explosive-grenade`): vendor 0.22.0 added + `not grantedEffect.fromItem` to the gem-name registration loop + (ModParser.lua:6423) — "Grenade" (fromItem) no longer shadows the + modFlagList phrases, so `grenade` / `for grenade skills` are live + `SkillType.Grenade` tags again (run-parsemod verified). The PR#53-era + extractor overrides (dead-entry / inert-SkillName rewrite, correct for + 0.21) were retired and `mod_parser_rules.json` + `parsed_mods.json` + regenerated. Deadeye's 3×15% grenade-CDR tree nodes (oracle + `extraModList` — new `ORACLE_EXTRA_STATS` tooling — Tree:21077/354/48429) + now apply: Speed 0.164→0.254 (1.00x), TotalDPS 0.53x→0.83x; gemling + Speed/AvgDamage/TotalDPS all 1.00-1.04x. + +Aggregate: off @5% 46→56 (@10% 55→62), dot 11→14 (@10% 13→19), def 25-col +410→413 (@10% 429→434), core-8 138→140, panel off 27→40 / 30→41. + +Remaining offence clusters after #4 (TotalDPS @5%, decomposed): the attack +AverageDamage family — smith-of-kitava 0.57x, monk-flicker 0.76x, +spirit-walker 0.78x (dot 0.23x), monk-twister 0.88x, titan 0.87x (its +CritMultiplier is a separate 0.5.4b "Bifurcated Crit Damage Bonus" MORE +mechanic, golden 5.00 vs PoBR 6.0) — plus blood-mage 0.87x, abyssal-lich +0.91x, pathfinder 0.90x, deadeye per-hit 0.83x (pre-0.5.4b shortfall, +documented in pob2_parity.rs), and frost-bomb 0.66x (golden unchanged +0.5.0→0.5.4b — a pre-existing cooldown-DPS gap, not a 0.5.4b regression). +These moved 6-46% in the 0.5.4b golden with per-build factors — no shared +constant; each needs its own oracle decomposition. + +Remaining elsewhere, re-triage against fresh `defenceModList` dumps: +ailment magnitude (#5) and the wolf-pack EHP remainder decomposed above +(Armour 0.98x / ChaosMaxHit 0.87x / Life 1.11x). Also ritualist TotalEHP +moved 1.04x→1.10x with #4a (低血 EHP 口径——vendor 只在显式 +`conditionLowLife` config 下 cap `LifeRecoverable`,PoBR 的 EHP 消费端 +尚未对齐该分支). Each is its own oracle-guided investigation; not all are +single formula constants (several are unmodeled unique/flask interactions). Also pending from the vendor bump itself: `check-buff-refs` reports 15 `vendor_ref` line-hash drifts in `data/overlay-common/buff_definitions.json` (e.g. OnslaughtFlask/ShapersPresence/UnholyMight) — the 4.5.4.3 upgrade swapped From 43dbc97263d0fcc277e4ada2aa1f00031cf3fef3 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 13:35:38 +0800 Subject: [PATCH 25/74] feat(parity): Blazing Critical global fire buff reaches all attacks (0.5.4b #5) Vendor 0.22.0 added a GlobalEffect/Buff tag to Blazing Critical's support_blazing_crits_gain_%_fire_damage_with_attacks_on_critical_hit (sup_int.lua:959), turning its 15% DamageGainAsFire (Attack + Condition:CritRecently) from a dead skill-local mod into a global player buff. Two generic consumption points: - stat_map_engine: admit DamageGainAsFire in the player-buff mod-name allowlist (consumer = the existing gain-as matrix in calc::damage). - support_buff_specs: judge support compatibility against additional granted effects too (mirrors buff_skill_specs) -- the compatible host for Blazing Critical under Charged Staff is the hidden Attack additional effect ChargedStaffShockwavePlayer (act_int.lua:3387). Ignite scales quadratically with the fire portion (chance ~ fire/ threshold, magnitude ~ fire): flicker TotalDotDPS 0.05x->0.72x, spirit-walker 0.23x->0.87x, monk-twister 0.44x->0.98x (its AverageDamage/TotalDPS also 0.60x->0.96x). --- .../pobr-build/src/calc_orchestrator/buffs.rs | 19 +++++++++++++------ crates/pobr-core/src/rules/stat_map_engine.rs | 8 ++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index c8747e3a..3ecb3da9 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -329,16 +329,23 @@ pub(crate) fn support_buff_specs(build: &Build, data: &BuildData) -> Vec = HashSet::new(); for group in build.enabled_socket_groups() { - // 组内已启用主动技能(效果已知且非 support)。 + // 组内已启用主动技能(效果已知且非 support)。含附加授予效果 + // (overlay/gem_effects.json 外键;vendor 对 additionalGrantedEffectId1..N + // 各建独立 activeSkill,support 对其同样逐个裁决——0.5.4b #5 案例: + // Charged Staff 的隐藏附加效果 ChargedStaffShockwavePlayer 是 Attack, + // Blazing Critical 借它兼容并全局授 buff)。 let active_ids: Vec<&str> = group .gem_skills .iter() - .filter(|g| { - data.granted_effects - .get(&g.skill_id) - .is_some_and(|e| !e.is_support) + .flat_map(|g| { + std::iter::once(g.skill_id.as_str()).chain( + data.gem_effects + .get(&g.skill_id) + .into_iter() + .flat_map(|l| l.additional_granted_effect_ids.iter().map(String::as_str)), + ) }) - .map(|g| g.skill_id.as_str()) + .filter(|id| data.granted_effects.get(*id).is_some_and(|e| !e.is_support)) .collect(); if active_ids.is_empty() { continue; diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index 9e3a11b5..d8878d5e 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -750,6 +750,14 @@ fn translate_player_buff_mod_name(name: &str) -> Result, Unsup // ModStore.lua:369;PoBR 编排层把 buff 载荷中 `Multiplier:` BASE 桥进 // cfg.multipliers,见 calc_orchestrator buff specs 注入点)。 "Multiplier:SigilOfPowerMaxStages" => Ok(vec!["Multiplier:SigilOfPowerMaxStages"]), + // (0.5.4b #5)Blazing Critical support(sup_int.lua:959):0.22.0 给 + // `support_blazing_crits_gain_%_fire_damage_with_attacks_on_critical_hit` + // 补上 GlobalEffect/Buff tag——15% `DamageGainAsFire` BASE(ModFlag.Attack + // + Condition:CritRecently)从「只挂在被支援技能上的死词条」变成全局玩家 + // buff("imbue all of your Attacks")。消费方 = `calc::damage` gain-as + // 矩阵(buildGainTable,`DamageGainAs` BASE 查询);点燃火源随之 + // 平方级放大(chance ∝ fire/threshold,magnitude ∝ fire)。 + "DamageGainAsFire" => Ok(vec!["DamageGainAsFire"]), other => Err(UnsupportedReason::UnknownModName(other.to_string())), } } From 8623c74cdae4c5a125b37643feed3ff04a463e94 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 13:35:38 +0800 Subject: [PATCH 26/74] test(parity): ratchet baselines for 0.5.4b #5 Blazing Critical off 56->58 @5% (62->64 @10%), dot 14->16 @5% (19->21 @10%); def/panel unchanged (CritRecently is combat-gated). Per-cell triage of the remaining dot misses recorded at the constants: all leftovers are either squared pass-through of the out-of-scope attack AvgDamage hit gaps or pre-0.5.4b items (smith Infernal Cry uptime gain, frost-bomb, essence-drain WithDotDPS). --- .../pobr-build/tests/parity/ninja_parity.rs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 17883164..29ca89a3 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -705,8 +705,18 @@ const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refract // 树词条(oracle extraModList 钉源 Tree:21077/354/48429)生效:deadeye Speed // 0.65x→1.00x(翻正)、TotalDPS 0.53x→0.83x;gemling Speed/AvgDamage/TotalDPS/ // CombinedDPS 全部 1.00-1.04x 翻正。 -const BASELINE_OFF_HIT5: usize = 56; // grenade 短语解禁后 56/80(Communion+Voices 52;mana-mult 46;迁移基线 39;0.5.0=71) -const BASELINE_OFF_HIT10: usize = 62; // grenade 短语解禁后 62/80(Communion+Voices 59;mana-mult 55;迁移基线 47;0.5.0=74) +// **0.5.4b #5 Blazing Critical 全局火焰 buff 重记(off +2 @5% 56→58 / +2 @10% +// 62→64)**:0.22.0 给 `support_blazing_crits_gain_%_fire_damage_with_attacks_ +// on_critical_hit` 补 GlobalEffect/Buff tag(sup_int.lua:959)——15% +// `DamageGainAsFire`(Attack + Condition:CritRecently)从死词条变成全局玩家 +// buff。两处接线:stat_map_engine 玩家 buff 允收名单 + support_buff_specs +// 裁决对象补附加授予效果(Charged Staff 的隐藏 Attack 附加效果 +// ChargedStaffShockwavePlayer 才是 Blazing Critical 的兼容宿主)。 +// monk-twister AverageDamage/TotalDPS 0.60x→0.96x 双列翻正;flicker +// TotalDPS 0.76x→0.84x、spirit-walker 0.78x→0.89x 收敛未入列(残余 = +// 攻击 AvgDamage 族存量缺口的平方传导)。 +const BASELINE_OFF_HIT5: usize = 58; // Blazing Critical 后 58/80(grenade 短语解禁 56;Communion+Voices 52;迁移基线 39;0.5.0=71) +const BASELINE_OFF_HIT10: usize = 64; // Blazing Critical 后 64/80(grenade 短语解禁 62;Communion+Voices 59;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -773,8 +783,19 @@ const BASELINE_OFF_HIT10: usize = 62; // grenade 短语解禁后 62/80(Communi // abyssal-lich dot 列随 hit 侧收敛入 @10% 带。 // **0.5.4b #4 grenade 短语解禁重记(dot +1 @5% 13→14 / +1 @10% 18→19)**: // gemling CombinedDPS 0.65x→1.04x 翻正(见 BASELINE_OFF_HIT5 上说明)。 -const BASELINE_DOT_HIT5: usize = 14; // grenade 短语解禁后 14/37(Communion+Voices 13;迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 19; // grenade 短语解禁后 19/37(Communion+Voices 18;迁移基线 11;0.5.0=28) +// **0.5.4b #5 Blazing Critical 重记(dot +2 @5% 14→16 / +2 @10% 19→21)**: +// 点燃火源随全局 15% DamageGainAsFire 平方级放大(chance ∝ fire/threshold × +// magnitude ∝ fire):monk-twister TotalDotDPS 0.44x→0.98x + CombinedDPS +// 0.60x→0.96x 翻正;flicker dot 0.05x→0.72x、spirit-walker 0.23x→0.87x 收敛 +// 未入列(残余同为 hit 侧存量缺口的平方传导,非 dot 侧机制)。其余 dot 脱靶 +// 逐格判定:deadeye 0.69x=hit 0.83x²、blood-mage 0.79x≈hit 0.87x²、titan/ +// abyssal/pathfinder 0.91-0.94x 随 hit 收敛;smith 0.20x 中 dot 特有残差 = +// Infernal Cry uptime-scaled DamageGainAsFire 12% 未建模(新旧 vendor 同值, +// 存量缺口非 0.5.4b 项);frost-bomb 0.87x / essence-drain WithDotDPS 1.36x +// golden 两版未动(存量);gemling dot 1.10x 高估 = fire/crit 小幅 hit 侧 +// 高估的下游传导(oracle 逐分量:fire 1.03x × stacks 1.05x × crit 混叠)。 +const BASELINE_DOT_HIT5: usize = 16; // Blazing Critical 后 16/37(grenade 短语解禁 14;Communion+Voices 13;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 21; // Blazing Critical 后 21/37(grenade 短语解禁 19;Communion+Voices 18;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 From 8a4ec9c7bcc0cb59542076e755967e43eba1e61f Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 13:35:38 +0800 Subject: [PATCH 27/74] docs: record 0.5.4b #5 ailment magnitude triage + Blazing Critical fix --- docs/adapting-to-0.5.4b.md | 56 +++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 2ae82dcd..6de161b1 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -179,8 +179,62 @@ documented in pob2_parity.rs), and frost-bomb 0.66x (golden unchanged These moved 6-46% in the 0.5.4b golden with per-build factors — no shared constant; each needs its own oracle decomposition. +**#5 target — ailment (ignite) magnitude. ✅ DONE (one root cause: Blazing +Critical global fire buff).** Re-triage first: the ailment *formula* did not +change in 0.5.4b — `IgniteChanceMultiplier`/`monsterAilmentThresholdTable`/ +`defaultAilmentDamageTypes`/the `ailmentDPSUncapped` assembly are byte-identical +between vendors (only `skillData.dpsMultiplier` → `output.DpsMultiplier`, a +refactor PoBR already mirrors via `dps_end_factors`). Yet 16 of 18 dot goldens +moved (1.07×–22.4×). Oracle decomposition on the extreme mover +(`monk-martial-artist-flicker-strike`, dot 0.05x, golden 47→1054) pinned a +single fire-specific ×4 on the hit: PoBR's stored-Fire range was 0.21x oracle +while every other damage type sat at the uniform 0.85x hit gap. Per-source +tabulation (`ORACLE_EXTRA_STATS=DamageGainAsFire`) showed the missing 15%: +the **Blazing Critical** support (`sup_int.lua:959`) — 0.22.0 added a +`GlobalEffect effectType=Buff` tag to its +`support_blazing_crits_gain_%_fire_damage_with_attacks_on_critical_hit` stat, +turning the 15% `DamageGainAsFire` (Attack + Condition:CritRecently) from a +dead skill-local mod (old-vendor oracle: absent from the skill modList, +IgniteDPS 47.54 = PoBR's 47.55 exactly) into a global player buff ("imbue all +of your Attacks with Fire"). Since ignite chance ∝ fireAvg/threshold and +magnitude ∝ fireAvg, the buff amplifies IgniteDPS quadratically (×16 on +flicker). Exactly the three worst dot movers carry the gem (flicker, +spirit-walker, monk-twister). + +Two generic consumption points (no per-gem code): +- `translate_player_buff_mod_name` allowlist admits `DamageGainAsFire` + (consumer = the existing gain-as matrix in `calc::damage::buildGainTable` + mirror; flags/Condition tags already translate). +- `support_buff_specs` now judges support compatibility against **additional + granted effects** too (mirroring `buff_skill_specs`): Blazing Critical's + host in these builds is Charged Staff, whose gem is Spell-typed — the + compatible skill is its hidden Attack additional effect + `ChargedStaffShockwavePlayer` (act_int.lua:3387). + +Result: monk-twister TotalDotDPS 0.44x→0.98x, CombinedDPS 0.60x→0.96x, +AverageDamage/TotalDPS 0.60x→0.96x (all into the 5% band); flicker dot +0.05x→0.72x, spirit-walker 0.23x→0.87x (remainder = the attack AvgDamage +family's hit gap squared, not a dot-side mechanic). Baselines: off 56→58 @5% +(62→64 @10%), dot 14→16 @5% (19→21 @10%); def/panel unchanged (CritRecently +is combat-gated, panel mode unaffected). No ailment canary was left ignored +(the suite's only `#[ignore]` is the vendor-dependent oracle differential). + +Dot-side leftovers, each triaged **not** a 0.5.4b ailment item: +- `smith-of-kitava` dot 0.20x: hit gap 0.57x squared explains 0.32; the extra + residual is the un-modeled *uptime-scaled* Infernal Cry `DamageGainAsFire` + 12.04% — present and identical in both vendors (old-vendor oracle proves it + predates 0.5.4b), i.e. a pre-existing warcry-uptime gap. +- `deadeye` 0.69x = hit 0.83x², `blood-mage` 0.79x ≈ hit 0.87x² (its ~16% + per-hit shortfall is the long-registered pre-0.5.4b item), `titan` 0.91x / + `abyssal-lich` 0.94x / `pathfinder` 0.92x track their hit gaps. +- `frost-bomb` dot 0.87x and `essence-drain` WithDotDPS 1.36x: goldens + byte-identical across the flip — pre-existing gaps. +- `gemling` dot 1.10x over: oracle per-component shows fire stored 1.03x × + stacks 1.05x × slight crit-chance overshoot — downstream of its small + hit-side overestimates, no dot-side mechanism. + Remaining elsewhere, re-triage against fresh `defenceModList` dumps: -ailment magnitude (#5) and the wolf-pack EHP remainder decomposed above +the wolf-pack EHP remainder decomposed above (Armour 0.98x / ChaosMaxHit 0.87x / Life 1.11x). Also ritualist TotalEHP moved 1.04x→1.10x with #4a (低血 EHP 口径——vendor 只在显式 `conditionLowLife` config 下 cap `LifeRecoverable`,PoBR 的 EHP 消费端 From b932cdf5bfded57f794916ae9b14931cc71a1250 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 13:47:25 +0800 Subject: [PATCH 28/74] data: re-review buff_definitions vendor_refs against PoB2 0.22.0 (ce8bffab) check-buff-refs reported 15 line-hash drifts after the 4.5.4.3 vendor bump. Root cause: the recorded hashes were pinned at vendor 2df5a743 (pre-0.21.0) and never refreshed, and doActorMisc shifted ~+75 lines across bumps (:503-765 -> :578-850; Arcane Surge block :1580-1591 -> :1606-1617). Per-buff manual review, diffing each pinned baseline segment against the 0.22.0 block: | buff | verdict | change | |-------------------|-----------|---------------------------------| | Adrenaline | cosmetic | line shift only, byte-identical | | ArcaneSurge | cosmetic | line shift only, byte-identical | | ChaoticMight | cosmetic | line shift only, byte-identical | | Convergence | cosmetic | line shift only, byte-identical | | Elusive | cosmetic | line shift only, byte-identical | | Fanaticism | cosmetic | line shift only, byte-identical | | Fortify | cosmetic | line shift only, byte-identical | | Freeze | cosmetic | line shift only, byte-identical | | HerEmbrace | cosmetic | line shift only, byte-identical | | MaddeningPresence | cosmetic | line shift only, byte-identical | | Malediction | cosmetic | line shift only, byte-identical | | Onslaught | cosmetic | line shift only, byte-identical | | OnslaughtFlask | cosmetic | same segment as Onslaught | | ShapersPresence | cosmetic | line shift only, byte-identical | | UnholyMight | cosmetic | one trailing space removed | Zero semantic changes, so no buff entry content changed and parity cannot move. Changes: - re-point all vendor_ref line ranges to the 0.22.0 locations, refresh the one stale hash (UnholyMight) via --write; check-buff-refs now reports 0 drift against vendor ce8bffab - update _meta.vendor_commit/subject to ce8bffab and fix the stale defs path in _meta.regen_command (file moved to data/overlay-common/) - update vendor line windows pinned by the vendor_refs_well_formed test and doc-comment line references - mark the docs/adapting-to-0.5.4b.md pending item done with the summary Validation: check-buff-refs 0 drift; cargo test -p pobr-gamedata (57 pass); cargo test -p pobr-build --test parity (58 pass, baselines unchanged); fmt + clippy -p pobr-gamedata --tests clean. --- .../tests/skills/load_buff_definitions.rs | 14 ++-- data/overlay-common/buff_definitions.json | 68 +++++++++---------- docs/adapting-to-0.5.4b.md | 14 ++-- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/crates/pobr-gamedata/tests/skills/load_buff_definitions.rs b/crates/pobr-gamedata/tests/skills/load_buff_definitions.rs index f5e7b994..f0a99e29 100644 --- a/crates/pobr-gamedata/tests/skills/load_buff_definitions.rs +++ b/crates/pobr-gamedata/tests/skills/load_buff_definitions.rs @@ -75,7 +75,7 @@ fn handler_budget() { assert!(handlers.contains(&"buff:onslaught_flask")); } -/// Onslaught 公式参数(vendor :539-570 基本形)。 +/// Onslaught 公式参数(vendor :614-645 基本形)。 #[test] fn onslaught_formula() { let doc = load(); @@ -97,7 +97,7 @@ fn onslaught_formula() { assert_eq!(def.mods[3].value, BuffModValue::PerEffect { coeff: 1.0 }); } -/// Adrenaline 逐 mod floor(vendor :586-593)。 +/// Adrenaline 逐 mod floor(vendor :661-668)。 #[test] fn adrenaline_per_mod_rounding() { let doc = load(); @@ -120,7 +120,7 @@ fn adrenaline_per_mod_rounding() { ); } -/// UnholyMight:Multiplier 字面量 + 0.3 系数 per-multiplier(vendor :577-581)。 +/// UnholyMight:Multiplier 字面量 + 0.3 系数 per-multiplier(vendor :652-656)。 #[test] fn unholy_might_shape() { let doc = load(); @@ -142,12 +142,12 @@ fn unholy_might_shape() { #[test] fn vendor_refs_well_formed() { let doc = load(); - // 允许的 vendor 行段:doActorMisc(CalcPerform :503-765,主体)+ - // Arcane Surge 注入段(CalcDefence :1580-1591,vendor 把该 buff 写在 + // 允许的 vendor 行段:doActorMisc(CalcPerform :578-850,主体)+ + // Arcane Surge 注入段(CalcDefence :1606-1617,vendor 把该 buff 写在 // doActorLifeManaSpirit 之后、recovery 之前——M4-n 条目)。 let allowed: &[(&str, u32, u32)] = &[ - ("Modules/CalcPerform.lua", 503, 765), - ("Modules/CalcDefence.lua", 1580, 1591), + ("Modules/CalcPerform.lua", 578, 850), + ("Modules/CalcDefence.lua", 1606, 1617), ]; for buff in &doc.buffs { assert!( diff --git a/data/overlay-common/buff_definitions.json b/data/overlay-common/buff_definitions.json index 3f88e557..649caa25 100644 --- a/data/overlay-common/buff_definitions.json +++ b/data/overlay-common/buff_definitions.json @@ -4,11 +4,11 @@ "Modules/CalcPerform.lua" ], "generator": "manual-curation (00-index 裁决 §4.2-4 批准的 overlay 通道例外)", - "regen_command": "人工归纳(doActorMisc :503-765 过程式 if-chain 无法 luajit 序列化);vendor 升级后跑 `cargo run -p sync-pob-catalog -- check-buff-refs --vendor-root vendor/PathOfBuilding-PoE2/src --defs data/4.5.0.3.4/overlay/buff_definitions.json` 对账,人工复核后 --write 刷新行段 hash", + "regen_command": "人工归纳(doActorMisc :578-850 过程式 if-chain 无法 luajit 序列化);vendor 升级后跑 `cargo run -p sync-pob-catalog -- check-buff-refs --vendor-root vendor/PathOfBuilding-PoE2/src --defs data/overlay-common/buff_definitions.json` 对账,人工复核后 --write 刷新行段 hash", "schema": "buff_definitions/v1", "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "2df5a7433dd2f1609e2fad8a6c3c917f923fe34f", - "vendor_commit_subject": "2df5a74 Fix crash when importing a character that uses Facebreaker gloves (#2098)" + "vendor_commit": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", + "vendor_commit_subject": "ce8bffa Fix Liminal Coil counting Marks as Curses (#2365)" }, "buffs": [ { @@ -78,8 +78,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 586, - "line_end": 593, + "line_start": 661, + "line_end": 668, "segment_hash": "fnv1a64:e3f46c64c93e2f77" }, "notes": "vendor 逐 mod m_floor(coeff × effectMod),effect 级不取整" @@ -125,8 +125,8 @@ "verified": true, "vendor_ref": { "file": "Modules/CalcDefence.lua", - "line_start": 1580, - "line_end": 1591, + "line_start": 1606, + "line_end": 1617, "segment_hash": "fnv1a64:144064cc425efef2" }, "notes": "基本形(默认 15% Cast Speed / 20% more ManaRegen × effect,effect=1+Σinc/100 不取整,vendor 无 floor)。未覆盖分支:Max(ArcaneSurgeCastSpeed/ManaRegen) 覆写、ArcaneSurgeLifeRegen flag(LifeRegen 改道)、ArcaneSurgeDamage(Spell Damage MORE)——均无树/装备样本,出现时升级 handler。" @@ -156,8 +156,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 582, - "line_end": 585, + "line_start": 657, + "line_end": 660, "segment_hash": "fnv1a64:dbd8f9c8a44c9452" } }, @@ -185,8 +185,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 594, - "line_end": 597, + "line_start": 669, + "line_end": 672, "segment_hash": "fnv1a64:4abf1b725bc2c12b" } }, @@ -198,8 +198,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 612, - "line_end": 631, + "line_start": 687, + "line_end": 706, "segment_hash": "fnv1a64:36b76d63b4ee74c6" }, "notes": "真逻辑:Max({source=Skill}) 查询 + Override 上限 + Nightblade 交互 + 输出字段 ElusiveEffectMod" @@ -212,8 +212,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 571, - "line_end": 576, + "line_start": 646, + "line_end": 651, "segment_hash": "fnv1a64:7d71030ed94f4da6" }, "notes": "真逻辑:selfCast 门控需 mainSkill.activeEffect.srcInstance 状态;公式 = floor(75×(1+BuffEffectOnSelf/100)) → Speed MORE / Cost INC -e / AoE INC(全 Cast flag)" @@ -226,8 +226,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 524, - "line_end": 538, + "line_start": 599, + "line_end": 613, "segment_hash": "fnv1a64:9e63cf1ecf4d6c25" }, "notes": "真逻辑:stacks 模型(Maximum/Minimum/Override Fortification + alliedFortify)→ DamageTakenWhenHit MORE -floor((1+BuffEffectOnSelf/100)×stacks);触发还含 Multiplier:Fortification > 0 分支" @@ -260,8 +260,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 684, - "line_end": 687, + "line_start": 760, + "line_end": 763, "segment_hash": "fnv1a64:b64bb12d1a1a4549" }, "notes": "calcLib.mod = (1+ΣINC/100)×ΠMORE;vendor m_max(m_floor(70×mod),0)——floor 在 clamp 前,与 effect 公式序一致" @@ -351,8 +351,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 598, - "line_end": 608, + "line_start": 673, + "line_end": 683, "segment_hash": "fnv1a64:7ccc1cb631cff09f" }, "notes": "全字面量(不吃 BuffEffectOnSelf 缩放)" @@ -382,8 +382,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 748, - "line_end": 751, + "line_start": 824, + "line_end": 827, "segment_hash": "fnv1a64:679897be912337f4" } }, @@ -412,8 +412,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 744, - "line_end": 747, + "line_start": 820, + "line_end": 823, "segment_hash": "fnv1a64:56f9043453784061" } }, @@ -472,8 +472,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 539, - "line_end": 570, + "line_start": 614, + "line_end": 645, "segment_hash": "fnv1a64:e6f99ba533f1ffe3" }, "notes": "基本形(不含 Silver Flask 分支——见 OnslaughtFlask handler 条目);effect = floor(10×(1+Σinc/100))" @@ -486,8 +486,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 539, - "line_end": 570, + "line_start": 614, + "line_end": 645, "segment_hash": "fnv1a64:e6f99ba533f1ffe3" }, "notes": "真逻辑:Silver Flask 来源时 effect 计入 flaskData.effectInc + FlaskEffect/MagicUtilityFlaskEffect INC(依赖 M3-T4 flask merge,M3 末接通)" @@ -509,8 +509,8 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 752, - "line_end": 754, + "line_start": 828, + "line_end": 830, "segment_hash": "fnv1a64:7b53c45448fcec95" } }, @@ -553,9 +553,9 @@ "verified": false, "vendor_ref": { "file": "Modules/CalcPerform.lua", - "line_start": 577, - "line_end": 581, - "segment_hash": "fnv1a64:6ffdd69243656a03" + "line_start": 652, + "line_end": 656, + "segment_hash": "fnv1a64:91e4f9947ce02683" }, "notes": "vendor 注释:Magnitude 经 Multiplier 实现(perStat 数据该阶段不可用);DamageGainAsChaos = 0.3×(1+BuffEffectOnSelf/100) per Multiplier" } diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 6de161b1..807e6b7f 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -240,11 +240,15 @@ moved 1.04x→1.10x with #4a (低血 EHP 口径——vendor 只在显式 `conditionLowLife` config 下 cap `LifeRecoverable`,PoBR 的 EHP 消费端 尚未对齐该分支). Each is its own oracle-guided investigation; not all are single formula constants (several are unmodeled unique/flask interactions). -Also pending from the vendor bump itself: `check-buff-refs` reports 15 -`vendor_ref` line-hash drifts in `data/overlay-common/buff_definitions.json` -(e.g. OnslaughtFlask/ShapersPresence/UnholyMight) — the 4.5.4.3 upgrade swapped -the vendor without the manual buff re-review; each drifted buff needs its -vendor lines re-checked for semantic changes, then `--write` to refresh. +✅ (2026-07-17) `check-buff-refs` 15-drift re-review done: the recorded hashes +were pinned at vendor `2df5a743` (pre-0.21.0; never refreshed at a82a33b either), +so every buff "drifted" purely because `doActorMisc` shifted ~+75 lines +(:503-765 → :578-850) and the Arcane Surge block moved :1580-1591 → :1606-1617. +All 15 blocks were diffed against the pinned baseline: 14 byte-identical, +UnholyMight differs by one removed trailing space — zero semantic changes, so +no `buff_definitions` entry content changed and parity is untouched. Line +ranges re-pointed to the 0.22.0 (`ce8bffab`) locations, hashes refreshed via +`--write`, `_meta.vendor_commit` updated; check now reports 0 drift. ## Tooling From ebebaba9423ec97f1a8a6b9e64df8448145488aa Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 14:19:52 +0800 Subject: [PATCH 29/74] fix(core): align bifurcate crit pipeline with vendor 0.22.0 Two crit fixes for the Garukhan's Resolve (BifurcateCrit) builds: - resolve_crit_multiplier: the bifurcated extra-crit-damage weight is the conditional probability (PreBifurcateCritChance^2/100)/CritChance (CalcOffence.lua:3823-3846, identical in 0.21/0.22 - PoBR had mis-ported it as the unconditional pre^2/10000). - weapon items: unflagged CriticalStrikeMultiplier mods now gain a Condition:MainHandAttack/OffHandAttack tag (Item.lua:1954-1961; 0.22.0 added CritMultiplier to the convert-to-local list), so a weapon's crit damage bonus no longer leaks into non-weapon attacks (Shield Wall) or spells. Routed via new CalculationSession::add_weapon_item; orchestrator only converts items whose base resolves as a weapon. Oracle-pinned on warrior-titan-shield-wall (CritMultiplier 6.02->5.00 golden exact; PreEffectiveCritMultiplier 4.88 reproduced). Parity: offensive 58->63 @5% (64->67 @10%), dot 16->19 @5%; spirit-walker CritMult 0.94x->1.00x, pathfinder 0.94x->0.99x (AvgDamage 0.90x->0.96x), titan CritMult 1.20x->1.00x. --- .../pobr-build/src/calc_orchestrator/mod.rs | 17 ++++++++-- crates/pobr-core/src/calc/crit.rs | 13 +++++-- crates/pobr-core/src/calc/session.rs | 11 ++++++ crates/pobr-core/src/ingest/item.rs | 34 +++++++++++++++++++ crates/pobr-core/src/lib.rs | 4 ++- crates/pobr-core/tests/offence/crit.rs | 8 +++-- 6 files changed, 78 insertions(+), 9 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index e92fcbd2..d899dd09 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -1852,9 +1852,20 @@ fn inject_items( filtered.modifier_texts = drop_spirit(filtered.modifier_texts); filtered.enchant_texts = drop_spirit(filtered.enchant_texts); } - session - .add_item(slot, &filtered) - .map_err(|e| BuildError::Parse(e.to_string()))?; + // 武器件走 add_weapon_item:无 flag 爆伤词条转按手条件 + // (vendor Item.lua:1954-1961,0.22.0 把 CritMultiplier 加进转换清单; + // 仅武器基底转换——Weapon2 的盾/箭袋/法器等非武器件不转)。 + let is_weapon_item = matches!(slot, EquipmentSlot::Weapon1 | EquipmentSlot::Weapon2) + && data.weapon_base(&item.base.to_string()).is_some(); + if is_weapon_item { + session + .add_weapon_item(slot, &filtered) + .map_err(|e| BuildError::Parse(e.to_string()))?; + } else { + session + .add_item(slot, &filtered) + .map_err(|e| BuildError::Parse(e.to_string()))?; + } // 槽位加成效果副本:该槽位有 `EffectOfBonusesFrom` INC 时,把本件已 // 注入词条的**数值差额副本** 追加注入(vendor CalcPerform.lua:1347-1369 diff --git a/crates/pobr-core/src/calc/crit.rs b/crates/pobr-core/src/calc/crit.rs index ef2713da..f24964d6 100644 --- a/crates/pobr-core/src/calc/crit.rs +++ b/crates/pobr-core/src/calc/crit.rs @@ -207,6 +207,7 @@ fn resolve_crit_impl( cfg, mode_effective, pre_bifurcate_pct, + crit_pct, bifurcate, inevitable, inevitable_less_more, @@ -254,6 +255,7 @@ fn resolve_crit_multiplier( cfg: &CalcConfig, mode_effective: bool, pre_bifurcate_pct: f64, + effective_crit_pct: f64, bifurcate: bool, inevitable: bool, inevitable_less_more: Option, @@ -289,11 +291,18 @@ fn resolve_crit_multiplier( * more }; - // 分岔:"两次都暴击"的概率额外加权一份爆伤(CalcOffence.lua L3796–3811), + // 分岔:条件概率「已知至少一次暴击时两次都暴击」额外加权一份爆伤 + // (CalcOffence.lua:3823-3846 `conditionalBifurcateChance = + // (PreBifurcateCritChance²/100) / CritChance`,vendor 0.21/0.22 同式), // 与必然互斥(必然路径已把 100% 暴击折进 less)。 if mode_effective && bifurcate && !inevitable { let bifurcate_multi_chance = pre_bifurcate_pct.powi(2) / 100.0; - extra += bifurcate_multi_chance * extra / 100.0; + let conditional = if effective_crit_pct > 0.0 { + bifurcate_multi_chance / effective_crit_pct + } else { + 0.0 + }; + extra += conditional * extra; } // 敌方 SelfCritMultiplier(标记等):BASE 加成 × (1 + INC/100)(CalcOffence.lua L3814–3825)。 diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index fdde004d..32334092 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -266,6 +266,17 @@ impl CalculationSession { Ok(()) } + /// 接入一件**武器**(调用方判定基底为武器):同 [`Self::add_item`],另按 vendor + /// `Item.lua:1954-1961` 把无 flag 的爆伤词条转为按手条件 + /// (见 [`crate::item::apply_weapon_hand_conditions`])。 + pub fn add_weapon_item(&mut self, slot: EquipmentSlot, item: &Item) -> Result<(), ParseError> { + let mut ingest = ingest_item_with_ctx(slot, item, self.parse_ctx())?; + crate::item::apply_weapon_hand_conditions(&mut ingest.modifiers, slot); + self.env.player.mod_db.add_list(ingest.modifiers); + self.unsupported_modifier_texts.extend(ingest.unsupported); + Ok(()) + } + /// 接入一件**激活态**药剂/护符(M3-T4 通道切换):词条经 /// [`crate::item::ingest_flask_charm`] 打包为 `FlaskBuff`/`CharmBuff` 载荷 /// List mod 注入(List 不参与 sum/more/flag 聚合 → 未合并前零直接影响),由 diff --git a/crates/pobr-core/src/ingest/item.rs b/crates/pobr-core/src/ingest/item.rs index 3bd8a648..106fa793 100644 --- a/crates/pobr-core/src/ingest/item.rs +++ b/crates/pobr-core/src/ingest/item.rs @@ -162,6 +162,40 @@ fn ingest_section( Ok(()) } +/// 武器件上的「转局部」词条:无 flag 的 `CriticalStrikeMultiplier`(vendor +/// `CritMultiplier`)加 `Condition:{Main,Off}HandAttack` tag,使其只作用于 +/// **用该武器攻击**的 hand pass(非武器攻击如 Shield Wall / 法术不吃)。 +/// +/// 对照 vendor `Item.lua:1954-1961`("Convert accuracy, crit damage bonus, … +/// to local"):0.22.0(0.5.4b)把 `CritMultiplier and mod.flags == 0` 加进 +/// 转换清单。守卫与 vendor 逐条对应:`mod.flags == 0`、 +/// `keywordFlags == 0 or KeywordFlag.Attack`、`not mod[1]`(无既有 tag)。 +/// +/// 仅由编排层对**武器**件调用(Weapon2 的盾/箭袋/法器不转换——vendor 该转换在 +/// `self.base.weapon` 分支内)。 +/// +/// ponytail: vendor 同清单还有 Accuracy/ImpaleChance/OnHit/leech 的转换 +/// (0.22.0 之前就有、PoBR 未建模的存量项);此处只落地 0.5.4b 增量 +/// CritMultiplier,其余待各自 oracle 钉值后按同一入口补。 +pub fn apply_weapon_hand_conditions(modifiers: &mut [Modifier], slot: EquipmentSlot) { + let var = match slot { + EquipmentSlot::Weapon1 => "MainHandAttack", + EquipmentSlot::Weapon2 => "OffHandAttack", + _ => return, + }; + for m in modifiers { + let keyword_ok = m.keyword_flags == KeywordFlags::NONE + || m.keyword_flags == KeywordFlags::ATTACK; + if m.name == ModName::from("CriticalStrikeMultiplier") + && m.flags == ModFlags::NONE + && keyword_ok + && m.tags.is_empty() + { + m.tags.push(ModTag::condition(var, false)); + } + } +} + /// 把物品词条 Multiplier tag 里的 `{SlotName}` 占位符替换为本件槽位 ID。 /// /// PoB2 在合并物品 mod 时按所在槽展开 `{SlotName}`(`calcLib.mod`)。典型来源 diff --git a/crates/pobr-core/src/lib.rs b/crates/pobr-core/src/lib.rs index 1e23204e..922c22ee 100644 --- a/crates/pobr-core/src/lib.rs +++ b/crates/pobr-core/src/lib.rs @@ -56,7 +56,9 @@ pub use attribute::trace::{ pub use display_catalog::{display_catalog, extract_display_values}; pub use ingest::campaign::{CampaignProgress, CampaignReward, CampaignState}; pub use ingest::character::CharacterBase; -pub use ingest::item::{ItemIngest, ItemModSection, ingest_item_with_ctx}; +pub use ingest::item::{ + ItemIngest, ItemModSection, apply_weapon_hand_conditions, ingest_item_with_ctx, +}; pub use ingest::item_text::{ItemTextError, parse_item_text, parse_pob_xml_item}; pub use ingest::passive::{AllocatedNode, PassiveIngest, ingest_passive_nodes_with_ctx}; pub use ingest::skill_source::{ diff --git a/crates/pobr-core/tests/offence/crit.rs b/crates/pobr-core/tests/offence/crit.rs index b68d81af..cf3e4924 100644 --- a/crates/pobr-core/tests/offence/crit.rs +++ b/crates/pobr-core/tests/offence/crit.rs @@ -199,9 +199,11 @@ fn bifurcate_crit_chance_50_to_75() { approx(crit.chance, 0.75); } -/// Bifurcate 额外爆伤:两次都暴击概率 = PreBifurcate²/100,额外加权一份 extra。 +/// Bifurcate 额外爆伤:条件概率「至少一次暴击时两次都暴击」额外加权一份 extra +/// (vendor CalcOffence.lua `conditionalBifurcateChance`)。 /// base_crit=50%,extra 基础 = (100)/100 = 1.0; -/// bifurcateMultiChance = 50²/100 = 25;extra' = 1.0 + 25*1.0/100 = 1.25 → crit_mult = 2.25。 +/// bifurcateMultiChance = 50²/100 = 25;有效暴击 = 75; +/// conditional = 25/75 = 1/3;extra' = 1.0×(1+1/3) → crit_mult = 2.3333。 #[test] fn bifurcate_adds_extra_crit_multiplier() { let mut db = player_with_base_crit(50.0); @@ -210,7 +212,7 @@ fn bifurcate_adds_extra_crit_multiplier() { let cfg = CalcConfig::spell().with_mode_effective(true); let crit = resolve_crit(&db, &enemy, &cfg, 1.0, 0.0, true); - approx(crit.multiplier, 2.25); + approx(crit.multiplier, 1.0 + 1.0 + 25.0 / 75.0); } // ─────────────────────────── Inevitable ─────────────────────────── From 9d5d8dddcdc76abce442838de196cf6630eb1830 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 14:39:52 +0800 Subject: [PATCH 30/74] fix(build): feed enemyDistance placeholder into skillDist (0.22.0) Vendor 0.22.0 changed CalcActiveSkill.lua:671 to 'effectiveRange = env.configInput.enemyDistance or env.configPlaceholder.enemyDistance' - the config placeholder (default 20) now feeds skillCfg.skillDist, activating DistanceRamp mods (Close Combat's 30% MORE ramps to 18% at distance 20). The old vendor read only the explicit , which is what PoBR mirrored, so ramped mods were dropped whole. Fallback chain now matches ConfigTab: explicit Input -> XML Placeholder -> catalog defaultPlaceholderState. Also adds a SkillDist probe + per-distance MoreDamage probes to the oracle (how the 30-vs-18 ramp base value was pinned). Parity: offensive 63->65 @5% (67->69 @10%), dot 19->21 @5%; flicker-strike AvgDamage/TotalDPS 0.838x->0.991x (in band), smith 0.575x->0.680x (Close Combat was one of its stacked gaps). --- .../pobr-build/src/calc_orchestrator/mod.rs | 27 ++++++++++++------- tools/pob2-oracle/oracle.lua | 10 +++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index d899dd09..d07616e3 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -791,21 +791,28 @@ fn stage_build_cfg(ctx: &mut StageCtx<'_>) { // (编排路径暂无 FlaskBuff/CharmBuff 载荷,T4 槽位接线后生效)+ 阶段 6 // buff_expander(trigger flag 未置仍零输出)。 .with_mode_combat(true); - // DistanceRamp 的 skillDist(vendor CalcActiveSkill.lua:655 `skillCfg.skillDist = - // env.mode_effective and env.configInput.enemyDistance`):仅 effective 口径 + - // enemyDistance 的 `` **显式值**(vendor `configInput`,不含 `` - // 占位值——后者经 ConfigTab apply 只兜底 `Multiplier:enemyDistance`(命中距离惩罚), - // 不喂 skillDist)。demo 套件 18 个 build 的 enemyDistance 全是 placeholder → 此处 - // None → Close/Far Combat 距离 MORE 整条跳过,与 golden 一致(PoB2 同样不应用)。 + // DistanceRamp 的 skillDist(vendor CalcActiveSkill.lua:671+684,0.22.0): + // `effectiveRange = env.configInput.enemyDistance or env.configPlaceholder.enemyDistance`, + // `skillDist = env.mode_effective and effectiveRange`。0.22.0 起 **placeholder + // 兜底喂 skillDist**(旧 vendor 只读显式 ``——彼时 demo 套件全 placeholder + // → None → Close Combat 距离 MORE 整条跳过)。回退链对齐 vendor ConfigTab: + // 显式 `` → XML `` → 目录 `defaultPlaceholderState` + // (ConfigTab.lua:559 对缺省项预填占位默认,enemyDistance = 20)。 let skill_distance = options .mode_effective .then(|| { - build - .config - .raw_inputs - .values + let raw = &build.config.raw_inputs; + raw.values .get("enemyDistance") + .or_else(|| raw.placeholders.get("enemyDistance")) .and_then(|v| v.as_number()) + .or_else(|| { + data.config_catalog + .as_deref() + .and_then(|c| c.get("enemyDistance")) + .and_then(|def| def.default.as_ref()) + .and_then(|d| d.placeholder_number) + }) }) .flatten(); cfg = cfg.with_skill_distance(skill_distance); diff --git a/tools/pob2-oracle/oracle.lua b/tools/pob2-oracle/oracle.lua index d8d72c0f..c18b00ea 100644 --- a/tools/pob2-oracle/oracle.lua +++ b/tools/pob2-oracle/oracle.lua @@ -426,6 +426,16 @@ local intermediates = {} -- Global increased/more damage intermediates.IncDamage = smlSum("INC", skillCfg, "Damage") intermediates.MoreDamage = smlMore(skillCfg, "Damage") +-- DistanceRamp probe: the cfg distance the engine evaluated ramp tags against. +intermediates.SkillDist = skillCfg and skillCfg.skillDist or nil +if skillCfg then + local copyTable = copyTable + for _, dist in ipairs({ 5, 20, 30, 40 }) do + local probe = copyTable(skillCfg, true) + probe.skillDist = dist + intermediates["MoreDamage_at" .. dist] = smlMore(probe, "Damage") + end +end -- Damaging-ailment magnitude breakdown (the `MagnitudeEffect` factor = -- calcLib.mod(skillModList, dotCfg, "AilmentMagnitude"), CalcOffence.lua:5145). From 9bccca948e8bb0131d7ccdff7b79beea00bf2a83 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 14:50:30 +0800 Subject: [PATCH 31/74] feat(core): support vendor PerStat statList in mod-parser rules The rule-driven parser rejected any tag_phrase whose PerStat carried statList instead of stat (e.g. ModParser.lua:1631 'per N armour and evasion on equipped shield' -> statList {ArmourOnWeapon 2, EvasionOnWeapon 2}), dropping the whole line as Unsupported. Normalize statList to a '|'-joined compound Multiplier var; the Multiplier eval branch splits and sums the per-part lookups before div/floor, matching vendor ModStore.lua:445-452 (stats summed, then one floor(sum/div)). The On multiplier keys were already prefilled by per_slot_defence_multipliers, so shield-scaling tree notables (smith/titan '4% increased Attack Damage per 75 Item Armour and Evasion on Equipped Shield', Tree:27687, oracle-pinned 88 INC) now land. Parity: offensive 65->67 @5% (69->71 @10%), dot 21->23 @5%; titan AvgDamage/TotalDPS 0.809x->0.972x (in band), smith 0.680x->0.800x; smith Physical pool now scale=9.5899 == oracle 57785/6025.65. --- crates/pobr-core/src/model/modifier.rs | 14 ++++++-- .../src/parse/mod_parser/template.rs | 32 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/crates/pobr-core/src/model/modifier.rs b/crates/pobr-core/src/model/modifier.rs index 901ce8e9..1d12c7b4 100644 --- a/crates/pobr-core/src/model/modifier.rs +++ b/crates/pobr-core/src/model/modifier.rs @@ -469,9 +469,17 @@ impl Modifier { // 取数源按 actor 维度切换(PoB2 ModStore.lua:347-353 `tag.actor` → // getActor(self, ...).modDB):None=当前 cfg.multiplier;Some= // actor_multipliers 快照(缺键=0,保守等价 PoB2 actor 缺位不生效)。 - let base = match actor { - None => cfg.multiplier(var), - Some(actor) => cfg.actor_multiplier(*actor, var), + // `|` 连接的复合 var(vendor PerStat `statList` 归一产物,见 + // mod_parser template):各分量取数求和后再 ÷div(vendor + // ModStore.lua:445-452 对 statList 逐项 GetStat 累加)。 + let lookup = |v: &str| match actor { + None => cfg.multiplier(v), + Some(actor) => cfg.actor_multiplier(*actor, v), + }; + let base = if var.contains('|') { + var.split('|').map(&lookup).sum() + } else { + lookup(var) }; // PoB2 ModStore.lua EvalMod(Multiplier L365 / PerStat L460): // `mult = m_floor(base / (tag.div or 1) + 0.0001)` —— 资源数除以 div 后向下取整 diff --git a/crates/pobr-core/src/parse/mod_parser/template.rs b/crates/pobr-core/src/parse/mod_parser/template.rs index 4c98b56b..56e1ae92 100644 --- a/crates/pobr-core/src/parse/mod_parser/template.rs +++ b/crates/pobr-core/src/parse/mod_parser/template.rs @@ -264,12 +264,32 @@ pub fn compile_tag(tag: &TagTemplate, captures: &[String]) -> Option { // M6.3 归一(C2):vendor `PerStat{stat,div,limit}` ↔ PoBR `Multiplier // {var=stat,div,limit}` 字段一一对应(计算侧 effective_number 只识别 // Multiplier;legacy 也统一产 Multiplier)。归一为 Multiplier。 - let stat = f - .get("stat") - .or_else(|| f.get("var")) - .and_then(|v| field_text(v, captures))?; - let stat = normalize_perstat_slot_suffix(&stat); - let stat = normalize_attribute_var(&stat); + // + // vendor `statList = {A, B, …}`(如 `per 75 armour and evasion on + // equipped shield` → {ArmourOnWeapon 2, EvasionOnWeapon 2}, + // ModParser.lua:1631):mult = floor(Σstats/div)——多 stat 求和后再 + // 除。归一为 `|` 连接的复合 var,取数端(effective_number Multiplier + // 分支)按 `|` 拆分求和,语义与 vendor ModStore.lua:445-452 一致。 + let stat = if let Some(StatMapValue::List(items)) = f.get("statList") { + let parts: Vec = items + .iter() + .map(|v| field_text(v, captures)) + .collect::>()?; + if parts.is_empty() { + return None; + } + parts + .into_iter() + .map(|p| normalize_attribute_var(&normalize_perstat_slot_suffix(&p))) + .collect::>() + .join("|") + } else { + let stat = f + .get("stat") + .or_else(|| f.get("var")) + .and_then(|v| field_text(v, captures))?; + normalize_attribute_var(&normalize_perstat_slot_suffix(&stat)) + }; let div = f .get("div") .and_then(|v| field_number(v, captures)) From 8f202a052876bb34347f33851c29b53e24fbda44 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 14:59:22 +0800 Subject: [PATCH 32/74] feat(core): hybrid mana-to-life cost + per-LifeCost mods (Atalui's Bloodletting) Three generic consumption points, no per-gem code: - stat-map PerStat tags now admit limit/limitTotal (vendor ModStore :461-468/:402-404), so Atalui's 'gain 2% of damage as physical per 20 life cost up to 40%' (PerStat stat=LifeCost, div=20, limit=40, limitTotal) maps instead of being rejected whole. - skill_mechanics grows calc_life_cost_hybrid + hybrid_life_cost_share: HybridManaAndLifeCost_Life BASE (vendor stat base_skill_cost_life_instead_of_mana_%, Atalui constantStat 100) turns floor(manaBase x supportMult) x hybrid into the Life cost base and the perform fill floors the residual mana cost x (1-hybrid) (CalcOffence.lua:2090-2104, :2160-2162). Cost inc/more/efficiency chain extracted into apply_cost_chain, shared by both paths. - orchestrator prefills cfg.stats/multipliers[LifeCost] from the new CalculationSession::life_cost_snapshot before aggregation, mirroring vendor's cost-before-damage ordering. Oracle-pinned on smith (LifeCost 309 -> floor(309/20)=15 -> +30% DamageGainAsPhysical; PoBR phys summed base 6051 vs oracle 6025). Parity: offensive 67->69 @5% (71->73 @10%), dot 23->24 @5%; smith AvgDamage/TotalDPS 0.800x->0.995x (in band). --- .../pobr-build/src/calc_orchestrator/mod.rs | 8 ++ crates/pobr-core/src/calc/perform.rs | 19 +++- crates/pobr-core/src/calc/session.rs | 17 ++++ crates/pobr-core/src/calc/skill_mechanics.rs | 88 +++++++++++++++---- crates/pobr-core/src/rules/stat_map_engine.rs | 16 +++- 5 files changed, 126 insertions(+), 22 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index d07616e3..1026b525 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -1522,6 +1522,14 @@ fn inject_per_x_multipliers(session: &mut CalculationSession, build: &Build, dat session.set_stat("Spirit", spirit_total); session.set_stat("Mana", mana_total); session.set_stat("Life", life_total); + // 主技能 Life 消耗快照(vendor output.LifeCost):per-life-cost 词条 + // (PerStat stat=LifeCost,如 Atalui's Bloodletting gain-as-physical)的取数源。 + // 消耗先于伤害结算,与 vendor CalcOffence 顺序一致。 + let life_cost = session.life_cost_snapshot(); + if life_cost > 0.0 { + session.set_stat("LifeCost", life_cost); + session.set_multiplier("LifeCost", life_cost); + } // per-槽位防御缩放(`On`):使 `+N to Armour per M Item Energy Shield on // Equipped Boots` 这类按某件装备防御值缩放的词条生效(PoB2 PerStat `On`)。 for (var, value) in per_slot_defence_multipliers(build, data) { diff --git a/crates/pobr-core/src/calc/perform.rs b/crates/pobr-core/src/calc/perform.rs index 79ea2e99..a4ae766d 100644 --- a/crates/pobr-core/src/calc/perform.rs +++ b/crates/pobr-core/src/calc/perform.rs @@ -12,7 +12,7 @@ use super::ailment::{ }; use super::output::StoredDamageRange; use super::skill_mechanics::{ - calc_aoe, calc_cooldown, calc_life_cost, calc_mana_cost, calc_projectile_count, + calc_aoe, calc_cooldown, calc_mana_cost, calc_projectile_count, calc_spirit_reservation, }; use super::trigger::{ @@ -909,13 +909,24 @@ fn fill_skill_mechanics(env: &mut Env) { } // 消耗:各资源需对应基础值 BASE 词条。无则跳过(保持 0)。 + // hybrid mana→life 转换(`HybridManaAndLifeCost_Life`,如 Atalui's Bloodletting / + // Blood-Magic 族):Life 侧吃 mana finalBase × hybrid,Mana 侧链尾 + // `floor((1-hybrid)×ManaCost)`(vendor CalcOffence.lua:2090-2104 + :2160-2162)。 + let hybrid = crate::calc::skill_mechanics::hybrid_life_cost_share(db, cfg); let base_mc = db.sum(ModType::Base, cfg, &[ModName::from("SkillManaCostBase")]); if base_mc > 0.0 { - env.player.output.mana_cost = calc_mana_cost(db, cfg, base_mc).final_cost; + let mana = calc_mana_cost(db, cfg, base_mc).final_cost; + env.player.output.mana_cost = if hybrid > 0.0 { + ((1.0 - hybrid) * mana).floor().max(0.0) + } else { + mana + }; } let base_lc = db.sum(ModType::Base, cfg, &[ModName::from("SkillLifeCostBase")]); - if base_lc > 0.0 { - env.player.output.life_cost = calc_life_cost(db, cfg, base_lc).final_cost; + if base_lc > 0.0 || (hybrid > 0.0 && base_mc > 0.0) { + env.player.output.life_cost = + crate::calc::skill_mechanics::calc_life_cost_hybrid(db, cfg, base_lc, base_mc) + .final_cost; } let base_sr = db.sum( ModType::Base, diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index 32334092..5a61418b 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -493,6 +493,23 @@ impl CalculationSession { .sum(ModType::Base, &self.env.cfg, &[ModName::from(name)]) } + /// 主技能 Life 消耗快照(vendor `output.LifeCost`;含 hybrid mana→life 转换)。 + /// 编排层在全部来源注入后调用,回填 `cfg.stats/multipliers["LifeCost"]` 供 + /// per-life-cost 词条(PerStat stat=LifeCost,如 Atalui's Bloodletting 的 + /// gain-as-physical)在伤害聚合期取数——与 vendor CalcOffence 先算 cost 再算 + /// 伤害的顺序等价。 + pub fn life_cost_snapshot(&self) -> f64 { + let base_mc = self.base_sum("SkillManaCostBase"); + let base_lc = self.base_sum("SkillLifeCostBase"); + super::skill_mechanics::calc_life_cost_hybrid( + &self.env.player.mod_db, + &self.env.cfg, + base_lc, + base_mc, + ) + .final_cost + } + /// 属性最终总量(PoB2 `calculateAttributes`,CalcPerform.lua:381-388: /// `output[stat] = m_max(round(calcLib.val(modDB, stat)), 0)`,calcLib.val = /// `Σbase × (1 + Σinc/100) × Πmore`)。`class_base` = 职业起始属性(PoBR 把它 diff --git a/crates/pobr-core/src/calc/skill_mechanics.rs b/crates/pobr-core/src/calc/skill_mechanics.rs index c80ffd85..aecc6d5a 100644 --- a/crates/pobr-core/src/calc/skill_mechanics.rs +++ b/crates/pobr-core/src/calc/skill_mechanics.rs @@ -599,13 +599,25 @@ pub fn calc_skill_cost( // 辅助宝石 cost 倍率:连乘截断 4 位小数 → 作用于 base → floor // (PoB2 CalcOffence.lua:2052/:2076-2077,先于 inc/more 链)。 - let support_mult_names = [ModName::from("SupportManaMultiplier")]; - let support_mult = { - let m = db.more(cfg, &support_mult_names); - (m * 10000.0).floor() / 10000.0 - }; - let base_cost_after_support = (base_cost * support_mult).floor(); + let base_cost_after_support = (base_cost * support_cost_multiplier(db, cfg)).floor(); + let final_cost = apply_cost_chain(db, cfg, resource_mod_prefix, base_cost_after_support); + + SkillCostResult { + kind, + base_cost, + final_cost: round(final_cost), + no_cost: false, + } +} +/// 消耗 inc/more/efficiency 链(vendor CalcOffence.lua:2126-2160 第二循环), +/// 入参 `final_base` = 已含 SupportManaMultiplier 的 finalBaseCost。 +fn apply_cost_chain( + db: &ModDb, + cfg: &CalcConfig, + resource_mod_prefix: &str, + final_base: f64, +) -> f64 { let type_cost_name = format!("{resource_mod_prefix}Cost"); let generic_cost_name = "Cost"; let inc_names = [ @@ -623,11 +635,10 @@ pub fn calc_skill_cost( // 1) `floor(finalBaseCost × (1+inc/100))` (inc 正 → floor,inc 负 → ceil) // 2) `floor/ceil(step1 × moreType)` (more < 1 → ceil) // 3) `floor/ceil(step2 × moreGeneric)` - // finalBaseCost = 上方 base_cost_after_support(已含 SupportManaMultiplier)。 let after_inc = if inc >= 0.0 { - (base_cost_after_support * (1.0 + inc / 100.0)).floor() + (final_base * (1.0 + inc / 100.0)).floor() } else { - (base_cost_after_support * (1.0 + inc / 100.0)).ceil() + (final_base * (1.0 + inc / 100.0)).ceil() }; let after_more_type = if more_type < 1.0 { (after_inc * more_type).ceil() @@ -652,17 +663,30 @@ pub fn calc_skill_cost( ModName::from("CostEfficiency"), ], ); - let final_cost = after_more / (1.0 + efficiency / 100.0); + after_more / (1.0 + efficiency / 100.0) +} - SkillCostResult { - kind, - base_cost, - final_cost: round(final_cost), - no_cost: false, - } +/// 辅助宝石 cost 倍率连乘,截断 4 位小数(PoB2 CalcOffence.lua:2062 +/// `mult = floor(More(skillCfg, "SupportManaMultiplier"), 4)`)。 +fn support_cost_multiplier(db: &ModDb, cfg: &CalcConfig) -> f64 { + let m = db.more(cfg, &[ModName::from("SupportManaMultiplier")]); + (m * 10000.0).floor() / 10000.0 +} + +/// Hybrid mana→life 消耗份额(0..=1)。来源 = `HybridManaAndLifeCost_Life` BASE +/// (vendor stat `base_skill_cost_life_instead_of_mana_%`,如 Atalui's Bloodletting +/// constantStat 100;Blood-Magic 族树词条同名),vendor 封顶 100 +/// (CalcOffence.lua:2067 `m_min(Sum(...), 100) / 100`)。 +pub fn hybrid_life_cost_share(db: &ModDb, cfg: &CalcConfig) -> f64 { + (db.sum(ModType::Base, cfg, &[ModName::from("HybridManaAndLifeCost_Life")]).min(100.0) / 100.0) + .max(0.0) } /// 方便的 Mana 消耗计算。 +/// +/// 注意:hybrid mana→life 转换([`hybrid_life_cost_share`] > 0)时 vendor 在链尾 +/// 追加 `floor((1 - hybrid) × ManaCost)`(CalcOffence.lua:2160-2162)——由调用方 +/// (perform fill)执行,本函数保持单资源纯链。 pub fn calc_mana_cost(db: &ModDb, cfg: &CalcConfig, base_mana_cost: f64) -> SkillCostResult { calc_skill_cost(db, cfg, SkillCostKind::Mana, "Mana", base_mana_cost) } @@ -672,6 +696,38 @@ pub fn calc_life_cost(db: &ModDb, cfg: &CalcConfig, base_life_cost: f64) -> Skil calc_skill_cost(db, cfg, SkillCostKind::Life, "Life", base_life_cost) } +/// Life 消耗(含 hybrid mana→life 转换,vendor CalcOffence.lua:2090-2104 Life 分支): +/// `life.finalBaseCost = round(base_life×mult + round(floor(base_mana×mult) × hybrid))`, +/// 之后走 Life 消耗 inc/more/efficiency 链。hybrid = 0 时与 [`calc_life_cost`] 等价。 +pub fn calc_life_cost_hybrid( + db: &ModDb, + cfg: &CalcConfig, + base_life_cost: f64, + base_mana_cost: f64, +) -> SkillCostResult { + let hybrid = hybrid_life_cost_share(db, cfg); + if hybrid <= 0.0 { + return calc_life_cost(db, cfg, base_life_cost); + } + if db.flag(cfg, ModName::from("HasNoCost")) { + return SkillCostResult { + kind: SkillCostKind::Life, + base_cost: base_life_cost, + final_cost: 0.0, + no_cost: true, + }; + } + let mult = support_cost_multiplier(db, cfg); + let mana_final_base = (base_mana_cost * mult).floor(); + let life_final_base = (base_life_cost * mult + (mana_final_base * hybrid).round()).round(); + SkillCostResult { + kind: SkillCostKind::Life, + base_cost: base_life_cost, + final_cost: round(apply_cost_chain(db, cfg, "Life", life_final_base)), + no_cost: false, + } +} + /// 方便的 Spirit(保留)消耗计算。 /// /// Spirit 保留量 = `ReservationMultiplier MORE`(宝石等级保留倍率)作用于宝石基础保留值, diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index d8878d5e..5dd6f5e5 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -1846,7 +1846,7 @@ pub fn translate_tag(tag: &BTreeMap) -> Result { - if !keys_subset_of(&["type", "stat", "div"]) { + if !keys_subset_of(&["type", "stat", "div", "limit", "limitTotal"]) { return Err(UnsupportedReason::UnsupportedTag(format!( "PerStat 含约定外键:{:?}", tag.keys().collect::>() @@ -1863,7 +1863,19 @@ pub fn translate_tag(tag: &BTreeMap) -> Result "Intelligence".to_string(), other => other.to_string(), }; - Ok(ModTag::multiplier(var, number("div").unwrap_or(1.0), None)) + // limit / limitTotal(vendor ModStore.lua:461-468 + :402-404;如 Atalui's + // Bloodletting `PerStat{stat=LifeCost,div=20,limit=40,limitTotal}`—— + // per 20 life cost 至多 +40% 总量封顶)。 + let mut mtag = + ModTag::multiplier(var, number("div").unwrap_or(1.0), number("limit")); + if let ( + ModTag::Multiplier { limit_total, .. }, + Some(StatMapValue::Bool(true)), + ) = (&mut mtag, tag.get("limitTotal")) + { + *limit_total = true; + } + Ok(mtag) } // 技能类型限定(vendor `{ type = "SkillType", skillType = SkillType.X }`, // 如 Garukhan `attacks_roll_crits_twice` 的 Attack 限定)→ From 4bf6d79c6b7124bc1ddd6a7068738c376c805090 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 15:08:37 +0800 Subject: [PATCH 33/74] fix(core): blend per-leg enemy mitigation in the crit short-circuit path The short-circuit path (legs identical, no CriticalStrike-conditional mods) mitigated only the non-crit leg and scaled by crit.effect. That identity holds for raw-independent mitigation (resists/taken chains) but not for enemy armour DR, which depends on the per-hit size: vendor's crit pass (CalcOffence.lua pass 1) computes DR against the larger post-crit-multiplier hit, giving crits a smaller reduction. Reconstruct the crit leg as non_crit x crit.multiplier and blend the two mitigated legs per vendor :4395; degenerates to the old formula bit-for-bit when mitigation has no raw dependency. Oracle-pinned on spirit-walker-twister (vendor PhysicalCritAverage/ StoredCritAvg = 0.9568 vs hit-leg 0.811; PoBR mitigated hit 42291 vs vendor AverageDamage 42318). Parity: offensive 69->71 @5%, dot 24->25; spirit-walker 0.941x->0.980x, monk-twister 0.958x->0.980x, titan ->0.978x, smith ->0.996x, flicker ->1.003x, ritualist ->1.000x - the whole attack AverageDamage family is now inside the 5%% band. abyssal-lich 0.912->0.926, blood-mage 0.869->0.880 (same root cause, partial). --- crates/pobr-core/src/calc/crit_pass.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/pobr-core/src/calc/crit_pass.rs b/crates/pobr-core/src/calc/crit_pass.rs index 0740a20e..8d599966 100644 --- a/crates/pobr-core/src/calc/crit_pass.rs +++ b/crates/pobr-core/src/calc/crit_pass.rs @@ -157,14 +157,27 @@ where // total = round(Σavg × crit.effect),I5 恒等保证与真 blend 数学相同。 let player_side: f64 = leg_total(&non_crit); let mitigated: f64 = if mode_effective { - leg_total_mitigated(&non_crit, &cfg_hit, &mitigation) + // 减伤侧**不能**走「单腿减伤 × crit.effect」:敌方护甲减伤依赖 + // 单次击中量(vendor pass1 用暴击后的击中量算 DR,暴击更大 → DR + // 更小),raw 依赖使 I5 恒等在减伤维度不成立。短路下暴击腿 = + // 非暴击腿 × crit.multiplier,据此按 vendor `:4395` 分腿减伤后 + // blend;减伤对 raw 无依赖(纯抗性/受伤链)时数学上退化为 + // 旧公式,逐值不变。 + let hit_leg = leg_total_mitigated(&non_crit, &cfg_hit, &mitigation); + let crit_leg_mitigated: f64 = non_crit + .avgs + .iter() + .zip(non_crit.raw_avgs.iter()) + .map(|((ty, avg), raw)| { + avg * crit.multiplier + * mitigation(&cfg_crit, *ty, raw * crit.multiplier) + }) + .sum(); + hit_leg * (1.0 - c) + crit_leg_mitigated * c } else { - player_side + player_side * crit.effect }; - ( - round(player_side * crit.effect), - round(mitigated * crit.effect), - ) + (round(player_side * crit.effect), round(mitigated)) } else { // 真双腿 blend(`:4395`):分腿过敌方减伤后按 c 加权。 let blend = |hit: f64, crit_v: f64| hit * (1.0 - c) + crit_v * c; From 907507741fc9ce2b2f84177e24eee445ba375832 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 15:15:09 +0800 Subject: [PATCH 34/74] =?UTF-8?q?fix(calc):=20close=20frost-bomb=20TotalDP?= =?UTF-8?q?S=200.66x=20=E2=80=94=20Archmage=20gain-as=20+=20curse=20effect?= =?UTF-8?q?=20chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backlog item #7-1 (pre-0.5.4b gap, golden unchanged across the flip). Oracle decomposition on monk-invoker-frost-bomb pinned two independent root causes: 1. Archmage buff not applied: 'DamageGainAsLightning' BASE 4 per 100 max Mana (vendor act_int.lua:229-231, PerStat Mana div 100) was dropped at two points of the player-buff statmap channel: - SkillType tag arm only accepted Attack/Spell and rejected 'neg' (Archmage payload is Channel-neg + Spell). Now resolves any enum name via the single-source SkillTypes::from_pob2_name (the old whitelist rationale predates full-enum skill_type_bits) and translates neg via the new ModTag::SkillTypesNeg (vendor ModStore.lua:829-833; new variant keeps the precompiled-cache Debug format of ModTag::SkillTypes byte-stable). - translate_player_buff_mod_name now admits DamageGainAsLightning (same consumer as the existing DamageGainAsFire: gain-as matrix). Lightning component now exact vs oracle (stored 1771.52 vs 1771.5). 2. Curse chain under-scaled Elemental Weakness (enemy resist 9 vs oracle -7; oracle enemyMitigation.resistMods: config 50, frost-bomb exposure -12, EW -45): - curse gem stats were fetched at raw gem level; vendor applyGemMods (CalcSetup.lua:410-435) applies '+N to Level of all Spell Skills' to every gem effect (EW 19+8 -> 27, payload -58 -> -66). - the skill-local CurseEffect segment of the curse multiplier (CalcPerform.lua:2423/:2427 reads skillModList) was missing: EW quality (+10) and group supports judged compatible via judge_group_supports (Heightened Curse +25). New stat_map_engine::curse_local_effect folds bare CurseEffect INC/MORE from the statmap global section; BuffSpec carries it as local_effect_inc/local_effect_more into the buff_pass curse branch. -66 x (1+35/100) x 0.5 (boss CurseEffectOnSelf) = -45, matching vendor exactly. Result: frost-bomb TotalDPS 4143.75 -> 6265.34 vs golden 6264.82 (0.66x -> 1.00x); CombinedDPS 0.66x -> 1.00x; dot 0.87x -> 1.07x. Collateral: druid-oracle-comet TotalDotDPS 1.04x -> 1.06x (its EW is absent from vendor's enemy resistMods — a pre-existing PoBR curse-slot overshoot now amplified; left as a tracked lead). Baselines (honest ratchet): off @5% 58->59, @10% 64->65; dot @10% 21->23 (@5% net zero: frost-bomb CombinedDPS in, druid dot out). Also adds POBR_DBG_ENEMYMIT per-type enemy mitigation dump used for the oracle diff. --- apps/pobr-cli/src/lib.rs | 4 + .../pobr-build/src/calc_orchestrator/buffs.rs | 77 ++++++++- .../pobr-build/tests/parity/ninja_parity.rs | 16 +- crates/pobr-core/src/calc/buff_pass.rs | 15 +- crates/pobr-core/src/calc/offence.rs | 16 ++ crates/pobr-core/src/calc/session.rs | 8 + crates/pobr-core/src/model/modifier.rs | 10 ++ .../src/parse/mod_parser/canonical.rs | 9 + crates/pobr-core/src/rules/stat_map_engine.rs | 161 ++++++++++++++++-- 9 files changed, 293 insertions(+), 23 deletions(-) diff --git a/apps/pobr-cli/src/lib.rs b/apps/pobr-cli/src/lib.rs index aac30be0..7acb6765 100644 --- a/apps/pobr-cli/src/lib.rs +++ b/apps/pobr-cli/src/lib.rs @@ -584,6 +584,10 @@ fn explain_tag(tag: &ModTag) -> TagExplain { kind: "SkillTypes".to_string(), detail: format!("限技能类型 {st:?}"), }, + ModTag::SkillTypesNeg(st) => TagExplain { + kind: "SkillTypesNeg".to_string(), + detail: format!("排除技能类型 {st:?}"), + }, ModTag::SkillName { names } => TagExplain { kind: "SkillName".to_string(), detail: format!("仅当主技能是 `{}` 时生效", names.join("` / `")), diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index 3ecb3da9..05ce281d 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -183,6 +183,8 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec socket_index, is_mark: false, ignore_curse_limit: false, + local_effect_inc: 0.0, + local_effect_more: 1.0, skill_types: pobr_data::skill::SkillTypes::NONE, }); } @@ -197,6 +199,8 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec socket_index, is_mark: false, ignore_curse_limit: false, + local_effect_inc: 0.0, + local_effect_more: 1.0, skill_types: pobr_data::skill::SkillTypes::NONE, }); } @@ -256,6 +260,8 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec socket_index, is_mark: false, ignore_curse_limit: false, + local_effect_inc: 0.0, + local_effect_more: 1.0, // vendor per-skill skillCfg(buff_pass 乘区对域限定词条—— // 「Banner Skills have N% increased Aura Magnitudes」的 // SkillTypes(Banner) tag——按本效果类型位匹配)。 @@ -265,8 +271,12 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec // curse 效果词条(M3-W4):statset stat 经 statmap curse 域映射 // 为敌侧 modifier(Despair→ChaosResist 减抗、Enfeeble→Damage MORE…), // buff_pass 施 CurseEffect 乘区 + Condition:Effective 后入 enemy db。 + // (存量 #7-1)取数等级 = 宝石等级 + 适用的 `+N to Level of all + // Skills`(vendor applyGemMods 对每个 gem effect 生效, + // CalcSetup.lua:410-435——EW 19+8→27,载荷 -58→-66)。 + let curse_level = gem.gem_level + additional_gem_levels(build, data, skill_id); let es = - data.effect_stats(skill_id, gem.gem_level, gem.quality, gem.stat_set_index); + data.effect_stats(skill_id, curse_level, gem.quality, gem.stat_set_index); let set_key = data.selected_set_key(skill_id, gem.stat_set_index); // vendor 注册前置(M4-l):buffList 仅由 GlobalEffect 载荷构成 // (CalcActiveSkill.lua:976-1041),curse 表项只从 buffList 构造 @@ -290,6 +300,14 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec continue; } let mods = curse_stat_modifiers(data, &es, skill_id, set_key.as_deref()); + // (存量 #7-1)技能局部 CurseEffect 段(vendor curse 乘区 + // CalcPerform.lua:2423/:2427 读 skillModList):curse 宝石 + // 自身品质段(EW `curse_effect_+%` 0.5/q)+ 组内**兼容** + // support(Heightened Curse constantStats +25、Atziri's + // Allure MORE -20)payload,经 statmap global 段 + // `curse_local_effect` 折算预汇入 spec。 + let (local_effect_inc, local_effect_more) = + curse_local_effect_scale(group, data, gem, skill_id, curse_level); specs.push(BuffSpec { name: buff_skill_name(data, skill_id), kind: BuffKind::Curse, @@ -300,6 +318,8 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec socket_index, is_mark, ignore_curse_limit: false, + local_effect_inc, + local_effect_more, skill_types: pobr_data::skill::SkillTypes::NONE, }); } @@ -309,6 +329,59 @@ pub(crate) fn buff_skill_specs(build: &Build, data: &BuildData) -> Vec specs } +/// (存量 #7-1)一个 curse 技能的**技能局部** CurseEffect 乘区段 +/// (vendor CalcPerform.lua:2423 `skillModList:Sum("INC", skillCfg, +/// "CurseEffect")` + :2427 `More(...)`): +/// - curse 宝石自身 effect stats(品质段携带 `curse_effect_+%`,如 EW 0.5/q); +/// - 组内**兼容** support([`judge_group_supports`] 四段裁决,与主技能支援 +/// 判定同源)的 effect stats(Heightened Curse constantStats `curse_effect_+%` +/// +25、Atziri's Allure `support_atziri_curse_effect_+%_final` MORE -20)。 +/// +/// stat → (INC, MORE) 折算走 statmap 数据([`stat_map_engine::curse_local_effect`], +/// global 段 `curse_effect_+%` → 裸 `CurseEffect INC`)。无 catalog → (0, 1)。 +fn curse_local_effect_scale( + group: &crate::build::SocketGroup, + data: &BuildData, + gem: &crate::build::GemSkillRef, + skill_id: &str, + curse_level: u32, +) -> (f64, f64) { + let Some(catalog) = resolve_stat_map_catalog(data) else { + return (0.0, 1.0); + }; + let (mut inc, mut more) = (0.0, 1.0); + let mut absorb = |effect_id: &str, level: u32, quality: u32, set_index: Option| { + let es = data.effect_stats(effect_id, level, quality, set_index); + let set_key = data.selected_set_key(effect_id, set_index); + for ds in es.all() { + if ds.value == 0.0 { + continue; + } + let (di, dm) = stat_map_engine::curse_local_effect( + &catalog, + effect_id, + set_key.as_deref(), + &ds.stat, + ds.value, + ); + inc += di; + more *= dm; + } + }; + absorb(skill_id, curse_level, gem.quality, gem.stat_set_index); + let judgement = super::triggers::judge_group_supports(group, data, skill_id); + for &i in &judgement.compatible { + let sup = &group.gem_skills[i]; + absorb( + &sup.skill_id, + sup.gem_level, + sup.quality, + sup.stat_set_index, + ); + } + (inc, more) +} + /// (M4-G)support 授予的**玩家侧 buff** → [`BuffSpec`](kind = [`BuffKind::Buff`], /// buff_pass Buff 分支 CalcPerform.lua:1949-1962 施 BuffEffect 乘区后并入 player db)。 /// @@ -385,6 +458,8 @@ pub(crate) fn support_buff_specs(build: &Build, data: &BuildData) -> Vec { skill_types.is_empty() || skill_types.intersects(cfg.skill_types) } + // 反选:位集命中 cfg 即失配(vendor neg 反转;空位集 → 恒生效, + // 与 vendor `skillTypes[nil]=false → not false` 一致)。 + ModTag::SkillTypesNeg(skill_types) => !skill_types.intersects(cfg.skill_types), // 具名技能限定(vendor ModStore.lua:752-780):主技能名任一命中; // cfg 无主技能名 → 不匹配(保守)。 ModTag::SkillName { names } => cfg @@ -556,6 +565,7 @@ impl Modifier { | ModTag::GlobalLimit { .. } | ModTag::DamageType(_) | ModTag::SkillTypes(_) + | ModTag::SkillTypesNeg(_) | ModTag::SkillName { .. } | ModTag::SlotName(_) => {} } diff --git a/crates/pobr-core/src/parse/mod_parser/canonical.rs b/crates/pobr-core/src/parse/mod_parser/canonical.rs index a454b4a9..7e98c357 100644 --- a/crates/pobr-core/src/parse/mod_parser/canonical.rs +++ b/crates/pobr-core/src/parse/mod_parser/canonical.rs @@ -133,6 +133,15 @@ fn canonical_tag(tag: &ModTag) -> String { format!("SkillTypes({})", hex.join(",")) } } + ModTag::SkillTypesNeg(st) => { + let w = st.words(); + if w[1..].iter().all(|&x| x == 0) { + format!("SkillTypesNeg({:#x})", w[0]) + } else { + let hex: Vec = w.iter().map(|x| format!("{x:#x}")).collect(); + format!("SkillTypesNeg({})", hex.join(",")) + } + } ModTag::SkillName { names } => format!("SkillName(names=[{}])", names.join(",")), ModTag::SlotName(s) => format!("SlotName({s})"), ModTag::DistanceRamp { ramp } => { diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index d8878d5e..3f5cf42e 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -430,6 +430,57 @@ pub fn has_curse_payload( .is_some_and(|entry| entry.mods.iter().any(is_curse_effect)) } +/// (存量 #7-1)curse 技能的**技能局部效果乘区**取数:stat 若映射为 +/// **无 GlobalEffect tag** 的 `CurseEffect` INC/MORE(留在 skillModList 的 +/// 技能局部 mod——vendor curse 乘区 CalcPerform.lua:2423/:2427 读 +/// `skillModList:Sum/More(skillCfg, "CurseEffect")`),返回其 +/// `(inc 增量, more 因子)`。典型来源 = curse 宝石自身品质 `curse_effect_+%` +/// (EW 0.5/q)、组内 Heightened Curse support(constantStats +25)、 +/// Atziri's Allure lineage(`support_atziri_curse_effect_+%_final` MORE -20)。 +/// +/// 保守口径:仅 kind=="mod"、无 tag、无 flag 的裸 `CurseEffect` 计入 +/// (Mark 门控变体带 SkillType tag,不计——mark 域另行建模时再放开); +/// 其余 stat / 形态返回 `(0.0, 1.0)`(零贡献,不误算)。 +pub fn curse_local_effect( + catalog: &StatMapCatalog, + effect_id: &str, + set_key: Option<&str>, + stat: &str, + stat_value: f64, +) -> (f64, f64) { + let (mut inc, mut more) = (0.0, 1.0); + let Some(entry) = catalog.lookup(effect_id, set_key, stat) else { + return (inc, more); + }; + if entry.unextractable { + return (inc, more); + } + let params = MergeParams { + div: entry.div, + mult: entry.mult, + base: entry.base, + value: entry.value, + }; + for element in &entry.mods { + if element.kind != "mod" + || element.name.as_deref() != Some("CurseEffect") + || !element.tags.is_empty() + || !element.flags.is_empty() + || !element.keyword_flags.is_empty() + || element.scalar.is_some() + { + continue; + } + let merged = params.merge(stat_value); + match element.mod_type.as_deref() { + Some("INC") => inc += merged, + Some("MORE") => more *= 1.0 + merged / 100.0, + _ => {} + } + } + (inc, more) +} + /// 元素是否为**曝光施加能力**载荷(M4-m,宿主探测用、非取数):vendor /// `flag("InflictExposure", …)`(SkillStatMap.lua:1692-1715 各 on_shock / /// on_cold_crit / on_ignite / on_hit 形)或 `ExposureChance BASE` @@ -758,6 +809,14 @@ fn translate_player_buff_mod_name(name: &str) -> Result, Unsup // 矩阵(buildGainTable,`DamageGainAs` BASE 查询);点燃火源随之 // 平方级放大(chance ∝ fire/threshold,magnitude ∝ fire)。 "DamageGainAsFire" => Ok(vec!["DamageGainAsFire"]), + // (存量 #7-1)Archmage(act_int.lua:229-231):`archmage_all_damage_%_to_ + // gain_as_lightning_to_grant_to_non_channelling_spells_per_100_max_mana` + // → `DamageGainAsLightning` BASE 4(GlobalEffect/Buff + SkillType + // Channel neg + SkillType Spell + PerStat Mana div 100)。消费方与 + // DamageGainAsFire 同 = `calc::damage` gain-as 矩阵;Mana 分母由编排层 + // `inject_per_x_multipliers` 预灌(cfg.multipliers["Mana"] = 全管线池值)。 + // monk-invoker-frost-bomb TotalDPS 0.66x 根因(缺 80% 闪电 gain-as)。 + "DamageGainAsLightning" => Ok(vec!["DamageGainAsLightning"]), other => Err(UnsupportedReason::UnknownModName(other.to_string())), } } @@ -1865,29 +1924,32 @@ pub fn translate_tag(tag: &BTreeMap) -> Result { - if !keys_subset_of(&["type", "skillType"]) { + if !keys_subset_of(&["type", "skillType", "neg"]) { return Err(UnsupportedReason::UnsupportedTag(format!( "SkillType 含约定外键:{:?}", tag.keys().collect::>() ))); } - let bits = match text("skillType").as_deref() { - Some("Attack") => SkillTypes::ATTACK, - Some("Spell") => SkillTypes::SPELL, - other => { - return Err(UnsupportedReason::UnsupportedTag(format!( - "SkillType 未支持类型:{other:?}" - ))); - } + let name = text("skillType"); + let Some(bits) = name.as_deref().and_then(SkillTypes::from_pob2_name) else { + return Err(UnsupportedReason::UnsupportedTag(format!( + "SkillType 未支持类型:{name:?}" + ))); }; - Ok(ModTag::SkillTypes(bits)) + if matches!(tag.get("neg"), Some(StatMapValue::Bool(true))) { + Ok(ModTag::SkillTypesNeg(bits)) + } else { + Ok(ModTag::SkillTypes(bits)) + } } // 距离插值(vendor `{ type = "DistanceRamp", ramp = {{d,m},...} }`,如 Close // Combat `support_close_combat_attack_damage_+%_final_from_distance`)→ @@ -2077,7 +2139,8 @@ mod tests { assert_eq!(mods[0].value.as_number(), Some(1.0), "div=100 → 分数"); } - /// 白名单外 flag 维持未知名上报;SkillType 未支持类型整条跳过。 + /// 白名单外 flag 维持未知名上报;SkillType 枚举外类型名整条跳过 + /// (枚举内类型经 `SkillTypes::from_pob2_name` 全量准入——存量 #7-1)。 #[test] fn flag_kind_outside_whitelist_or_unknown_skill_type_unsupported() { let entry = entry_json( @@ -2090,7 +2153,7 @@ mod tests { let entry = entry_json( r#"{ "mods": [ { "kind": "flag", "name": "BifurcateCrit", "mod_type": "FLAG", - "tags": [ { "type": "SkillType", "skillType": "Minion" } ] } ] }"#, + "tags": [ { "type": "SkillType", "skillType": "NotARealSkillType" } ] } ] }"#, ); assert!(matches!( map_entry(&entry, 1.0), @@ -2098,6 +2161,27 @@ mod tests { )); } + /// (存量 #7-1)SkillType tag:枚举内类型名全量准入(单源 + /// `SkillTypes::from_pob2_name`);`neg = true` → [`ModTag::SkillTypesNeg`] + /// (vendor ModStore.lua:829-833 反选,如 Archmage 的 non-channelling 限定)。 + #[test] + fn skill_type_tag_full_enum_and_neg_translate() { + use pobr_data::skill::SkillTypes; + let mut tag = BTreeMap::new(); + tag.insert("type".into(), StatMapValue::Text("SkillType".into())); + tag.insert("skillType".into(), StatMapValue::Text("Minion".into())); + assert_eq!( + translate_tag(&tag).unwrap(), + ModTag::SkillTypes(SkillTypes::MINION) + ); + tag.insert("skillType".into(), StatMapValue::Text("Channel".into())); + tag.insert("neg".into(), StatMapValue::Bool(true)); + assert_eq!( + translate_tag(&tag).unwrap(), + ModTag::SkillTypesNeg(SkillTypes::CHANNEL) + ); + } + /// CritChanceCap 直通(Garukhan constant stat 50 → OVERRIDE)。 #[test] fn crit_chance_cap_override_passthrough() { @@ -2700,6 +2784,47 @@ mod tests { ); } + /// (存量 #7-1)curse 技能局部效果乘区取数:裸 `CurseEffect` INC/MORE 计入, + /// 带 GlobalEffect / 其他 tag 的元素与无关 stat 归零贡献。 + #[test] + fn curse_local_effect_collects_bare_curse_effect_only() { + let catalog = catalog_json( + r#"{ + "global": { + "curse_effect_+%": { "mods": [ { "kind": "mod", "name": "CurseEffect", "mod_type": "INC" } ] }, + "support_atziri_curse_effect_+%_final": { "mods": [ { "kind": "mod", "name": "CurseEffect", "mod_type": "MORE" } ] }, + "mark_effect_+%": { "mods": [ { "kind": "mod", "name": "CurseEffect", "mod_type": "INC", + "tags": [ { "type": "SkillType", "skillType": "Mark" } ] } ] } + }, + "per_stat_set": {} + }"#, + ); + assert_eq!( + curse_local_effect(&catalog, "X", None, "curse_effect_+%", 25.0), + (25.0, 1.0) + ); + assert_eq!( + curse_local_effect( + &catalog, + "X", + None, + "support_atziri_curse_effect_+%_final", + -20.0 + ), + (0.0, 0.8) + ); + // 带 tag(Mark 门控)的变体保守不计。 + assert_eq!( + curse_local_effect(&catalog, "X", None, "mark_effect_+%", 30.0), + (0.0, 1.0) + ); + // 无关 stat / 无条目 → 零贡献。 + assert_eq!( + curse_local_effect(&catalog, "X", None, "nope", 1.0), + (0.0, 1.0) + ); + } + // ---- W-J:isGlobalEffect / global-only merge ---- /// isGlobalEffect 等价(CalcActiveSkill.lua:68-80):单 mod 查自身 tags; From 8d99a299542be4f0679398341a4ca3e670a1c88d Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 15:23:05 +0800 Subject: [PATCH 35/74] chore(parity): raise baselines for the 0.5.4b #6 AvgDamage-family fixes - offensive 58->71 @5% (64->73 @10%), dot 16->25 @5% (21->27 @10%), panel offensive 40->41 / 41->42; defensive unchanged (413/434, core-8 140). Reason chain recorded next to each constant (bifurcate conditional + weapon hand-condition, enemyDistance placeholder -> skillDist, PerStat statList, Atalui hybrid life cost, per-leg mitigation blend). - regenerate parsed_mods.json + parse-coverage.json (statList tree lines flip unsupported -> parsed; coverage 0.8037 -> 0.8042). - document #6 in docs/adapting-to-0.5.4b.md with per-build decomposition and the triaged remainders (deadeye pre-0.5.4b per-hit, Mageblood family on blood-mage/abyssal-lich, frost-bomb cooldown DPS, smith Infernal Cry warcry-uptime dot residual, Barrage Repeats DPS channel). - cargo fmt reflow on touched files. --- .../pobr-build/tests/parity/ninja_parity.rs | 46 ++++++++-- crates/pobr-core/src/calc/crit_pass.rs | 3 +- crates/pobr-core/src/calc/perform.rs | 3 +- crates/pobr-core/src/calc/skill_mechanics.rs | 8 +- crates/pobr-core/src/ingest/item.rs | 4 +- crates/pobr-core/src/rules/stat_map_engine.rs | 9 +- data/4.5.4.3/generated/parse-coverage.json | 30 +++---- data/4.5.4.3/generated/parsed_mods.json | 26 +++++- docs/adapting-to-0.5.4b.md | 86 +++++++++++++++++++ 9 files changed, 179 insertions(+), 36 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 29ca89a3..7544b53b 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -715,8 +715,35 @@ const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refract // monk-twister AverageDamage/TotalDPS 0.60x→0.96x 双列翻正;flicker // TotalDPS 0.76x→0.84x、spirit-walker 0.78x→0.89x 收敛未入列(残余 = // 攻击 AvgDamage 族存量缺口的平方传导)。 -const BASELINE_OFF_HIT5: usize = 58; // Blazing Critical 后 58/80(grenade 短语解禁 56;Communion+Voices 52;迁移基线 39;0.5.0=71) -const BASELINE_OFF_HIT10: usize = 64; // Blazing Critical 后 64/80(grenade 短语解禁 62;Communion+Voices 59;迁移基线 47;0.5.0=74) +// **0.5.4b #6 攻击 AvgDamage 族攻坚重记(off +13 @5% 58→71 / +9 @10% 64→73)**: +// 五个通用消费点修复叠加,整族(smith/titan/flicker/monk-twister/spirit-walker/ +// pathfinder/ritualist/gemling)全部进入 5% 带: +// 1. Bifurcate 爆伤条件概率(vendor :3823-3846 `conditionalBifurcateChance = +// (PreBifurcate²/100)/CritChance`,PoBR 误移植为无条件 pre²/10000——新旧 vendor +// 同式,属存量误差)+ 武器无 flag 爆伤词条转按手条件(Item.lua:1954-1961, +// **0.22.0 新增** CritMultiplier 进转换清单):spirit-walker/pathfinder CritMult +// 0.94x→1.00x/0.99x,titan CritMult 1.20x→1.00x(oracle 钉值 6.02→5.00 golden 精确)。 +// 2. enemyDistance placeholder 喂 skillDist(CalcActiveSkill.lua:671,**0.22.0 新增** +// configPlaceholder 兜底):Close Combat 30% MORE 按 ramp(20)=0.6 生效 → +// flicker 0.838x→0.99x、smith +Close Combat 段。 +// 3. PerStat statList 支持(ModParser.lua:1631 `per 75 armour and evasion on +// equipped shield` → `|` 复合 Multiplier var 求和):smith/titan 盾防御缩放 +// tree notable(Tree:27687,oracle 钉值 88 INC)生效。 +// 4. hybrid mana→life cost + per-LifeCost 词条(Atalui's Bloodletting: +// `base_skill_cost_life_instead_of_mana_%` 100 + PerStat{stat=LifeCost,div=20, +// limit=40,limitTotal},vendor :2067/:2090-2104):smith +30% DamageGainAsPhysical +// (oracle LifeCost 309 → floor(309/20)=15 → 30)。 +// 5. 暴击短路路径分腿减伤 blend(vendor :4395;敌方护甲 DR 依赖单次击中量, +// crit 腿用暴击后击中算 DR):spirit-walker 0.94x→0.98x、blood-mage/abyssal-lich +// 连带收敛(0.87x→0.88x / 0.91x→0.93x,未入列)。 +// 逐 build:smith 0.575x→0.996x、titan 0.874x→0.978x、flicker 0.838x→1.003x、 +// spirit-walker 0.892x→0.980x、monk-twister 0.958x→0.980x、pathfinder 0.904x→ +// 0.963x、ritualist 0.989x→1.000x。剩余脱靶:deadeye 0.832x(pre-0.5.4b per-hit +// 欠条)、blood-mage 0.880x / abyssal-lich 0.926x(Mageblood 词条族缺口,Phase 1 +// 独立项——oracle 钉值 blood-mage 缺 `INC CritChance 107 'Mageblood'`)、 +// frost-bomb 0.661x(golden 两版未动,存量冷却 DPS 缺口)。 +const BASELINE_OFF_HIT5: usize = 71; // 0.5.4b #6 AvgDamage 族后 71/80(Blazing Critical 58;grenade 短语解禁 56;Communion+Voices 52;迁移基线 39;0.5.0=71) +const BASELINE_OFF_HIT10: usize = 73; // 0.5.4b #6 AvgDamage 族后 73/80(Blazing Critical 64;grenade 短语解禁 62;Communion+Voices 59;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -794,8 +821,15 @@ const BASELINE_OFF_HIT10: usize = 64; // Blazing Critical 后 64/80(grenade // 存量缺口非 0.5.4b 项);frost-bomb 0.87x / essence-drain WithDotDPS 1.36x // golden 两版未动(存量);gemling dot 1.10x 高估 = fire/crit 小幅 hit 侧 // 高估的下游传导(oracle 逐分量:fire 1.03x × stacks 1.05x × crit 混叠)。 -const BASELINE_DOT_HIT5: usize = 16; // Blazing Critical 后 16/37(grenade 短语解禁 14;Communion+Voices 13;迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 21; // Blazing Critical 后 21/37(grenade 短语解禁 19;Communion+Voices 18;迁移基线 11;0.5.0=28) +// **0.5.4b #6 AvgDamage 族 dot 列跟涨重记(dot +9 @5% 16→25 / +6 @10% 21→27)**: +// 点燃 ∝ 火源²,hit 侧整族闭合后 dot 列自动跟正(flicker 0.72x→0.87✓ 附近、 +// spirit-walker 0.87x→0.98x✓、titan/pathfinder/monk-twister CombinedDPS 随 hit +// 翻正)。剩余脱靶:smith TotalDotDPS 0.40x(= 火源比 0.63² —— Infernal Cry +// uptime-scaled DamageGainAsFire 12.04% 未建模,新旧 vendor 同值的存量 warcry +// uptime 机制,见 BASELINE_OFF_HIT5 注);deadeye 0.69x = hit 0.83x²; +// blood-mage 0.79x(Mageblood);gemling 1.10x 高估(存量)。 +const BASELINE_DOT_HIT5: usize = 25; // 0.5.4b #6 后 25/37(Blazing Critical 16;grenade 短语解禁 14;Communion+Voices 13;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 27; // 0.5.4b #6 后 27/37(Blazing Critical 21;grenade 短语解禁 19;Communion+Voices 18;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 @@ -813,8 +847,8 @@ const BASELINE_DOT_HIT10: usize = 21; // Blazing Critical 后 21/37(grenade /// (template.rs / special_mod.rs)同 commit 全量化——一批 `ModTag::SkillTypes` /// 域词条(Area/Projectile/Grenade 等)在 panel 口径开始正确匹配。effective /// 主口径与防御/进攻/dot 主基线逐值持平(纯 panel 侧收敛)。 -const PANEL_OFF_HIT5: usize = 40; // 0.5.4b #4(Communion+Voices 38;grenade 短语解禁 +2);迁移基线 27;0.5.0=44 -const PANEL_OFF_HIT10: usize = 41; // 0.5.4b #4(Communion+Voices 39;grenade 短语解禁 +2);迁移基线 30;0.5.0=46 +const PANEL_OFF_HIT5: usize = 41; // 0.5.4b #6 +1(#4 后 40;Communion+Voices 38);迁移基线 27;0.5.0=44 +const PANEL_OFF_HIT10: usize = 42; // 0.5.4b #6 +1(#4 后 41;Communion+Voices 39);迁移基线 30;0.5.0=46 /// 回归门禁:聚合命中数不得低于已记录基线([`BASELINE_*`])。CI gate,防止改动倒退 parity。 #[test] diff --git a/crates/pobr-core/src/calc/crit_pass.rs b/crates/pobr-core/src/calc/crit_pass.rs index 8d599966..c9dcd586 100644 --- a/crates/pobr-core/src/calc/crit_pass.rs +++ b/crates/pobr-core/src/calc/crit_pass.rs @@ -169,8 +169,7 @@ where .iter() .zip(non_crit.raw_avgs.iter()) .map(|((ty, avg), raw)| { - avg * crit.multiplier - * mitigation(&cfg_crit, *ty, raw * crit.multiplier) + avg * crit.multiplier * mitigation(&cfg_crit, *ty, raw * crit.multiplier) }) .sum(); hit_leg * (1.0 - c) + crit_leg_mitigated * c diff --git a/crates/pobr-core/src/calc/perform.rs b/crates/pobr-core/src/calc/perform.rs index a4ae766d..5dbf4ed0 100644 --- a/crates/pobr-core/src/calc/perform.rs +++ b/crates/pobr-core/src/calc/perform.rs @@ -12,8 +12,7 @@ use super::ailment::{ }; use super::output::StoredDamageRange; use super::skill_mechanics::{ - calc_aoe, calc_cooldown, calc_mana_cost, calc_projectile_count, - calc_spirit_reservation, + calc_aoe, calc_cooldown, calc_mana_cost, calc_projectile_count, calc_spirit_reservation, }; use super::trigger::{ RotationSkill, TriggerSourceStats, calc_cwc_trigger_rate_traced, calc_multi_spell_rotation, diff --git a/crates/pobr-core/src/calc/skill_mechanics.rs b/crates/pobr-core/src/calc/skill_mechanics.rs index aecc6d5a..2a0a667c 100644 --- a/crates/pobr-core/src/calc/skill_mechanics.rs +++ b/crates/pobr-core/src/calc/skill_mechanics.rs @@ -678,7 +678,13 @@ fn support_cost_multiplier(db: &ModDb, cfg: &CalcConfig) -> f64 { /// constantStat 100;Blood-Magic 族树词条同名),vendor 封顶 100 /// (CalcOffence.lua:2067 `m_min(Sum(...), 100) / 100`)。 pub fn hybrid_life_cost_share(db: &ModDb, cfg: &CalcConfig) -> f64 { - (db.sum(ModType::Base, cfg, &[ModName::from("HybridManaAndLifeCost_Life")]).min(100.0) / 100.0) + (db.sum( + ModType::Base, + cfg, + &[ModName::from("HybridManaAndLifeCost_Life")], + ) + .min(100.0) + / 100.0) .max(0.0) } diff --git a/crates/pobr-core/src/ingest/item.rs b/crates/pobr-core/src/ingest/item.rs index 106fa793..f8e8a55f 100644 --- a/crates/pobr-core/src/ingest/item.rs +++ b/crates/pobr-core/src/ingest/item.rs @@ -184,8 +184,8 @@ pub fn apply_weapon_hand_conditions(modifiers: &mut [Modifier], slot: EquipmentS _ => return, }; for m in modifiers { - let keyword_ok = m.keyword_flags == KeywordFlags::NONE - || m.keyword_flags == KeywordFlags::ATTACK; + let keyword_ok = + m.keyword_flags == KeywordFlags::NONE || m.keyword_flags == KeywordFlags::ATTACK; if m.name == ModName::from("CriticalStrikeMultiplier") && m.flags == ModFlags::NONE && keyword_ok diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index 5dd6f5e5..8613b105 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -1866,12 +1866,9 @@ pub fn translate_tag(tag: &BTreeMap) -> Result` (what PoBR mirrored), so every DistanceRamp + mod was dropped whole. Close Combat II's 30% MORE (ramp {10:1, 35:0}, + evaluated at the placeholder distance 20 → ×0.6 = 18%) now lands. Fallback + chain mirrors ConfigTab: Input → XML Placeholder → catalog + `defaultPlaceholderState`. flicker 0.838x→0.99x; smith one of three stacked + segments. (Oracle probes added: `intermediates.SkillDist` + + `MoreDamage_at{5,20,30,40}` — this is how the 30-vs-18 ramp base was pinned.) +- **#6c PerStat `statList` (smith/titan shield notable Tree:27687, "4% + increased Attack Damage per 75 Item Armour and Evasion on Equipped + Shield", ModParser.lua:1631).** The rule-driven parser rejected statList + tag phrases whole. Normalized to a `|`-joined compound Multiplier var, + summed at eval (ModStore.lua:445-452 semantics: Σstats then one + floor(sum/div)). Oracle-pinned 88 INC; smith Physical pool scale lands at + 9.5899 = oracle exactly. +- **#6d hybrid mana→life cost + per-LifeCost mods (smith's Atalui's + Bloodletting).** `base_skill_cost_life_instead_of_mana_%` → + `HybridManaAndLifeCost_Life` turns `floor(manaBase × supportMult) × hybrid` + into the Life cost base (CalcOffence.lua:2067/:2090-2104; mana chain tail + `floor((1-hybrid)×ManaCost)`); statmap PerStat now admits + `limit`/`limitTotal` so the support's `PerStat{stat=LifeCost, div=20, + limit=40, limitTotal}` gain-as-physical maps; the orchestrator prefills + `cfg.stats/multipliers[LifeCost]` from the new + `CalculationSession::life_cost_snapshot` (vendor's cost-before-damage + ordering). Oracle LifeCost 309 → floor(309/20)=15 → +30% + DamageGainAsPhysical. +- **#6e per-leg enemy mitigation in the crit short-circuit path.** The + short-circuit (identical legs) mitigated the non-crit leg and scaled by + crit.effect — valid only for raw-independent mitigation; enemy armour DR + depends on hit size (vendor pass 1 computes DR against the post-crit-mult + hit, CalcOffence.lua:4395 blend). Reconstruct the crit leg as + `non_crit × crit.multiplier` and blend per-leg; bit-identical when + mitigation has no raw dependency. spirit-walker mitigated hit 42291 vs + vendor AverageDamage 42318 (0.9994). + +Aggregate: off @5% 58→71 (@10% 64→73), dot 16→25 (@10% 21→27), panel off +40→41/41→42; def unchanged. Per-build TotalDPS (effective): smith 0.575x→0.996x, +titan 0.874x→0.978x, flicker 0.838x→1.003x, spirit-walker 0.892x→0.980x, +monk-twister 0.958x→0.980x, pathfinder 0.904x→0.963x, ritualist →1.000x. + +Remaining offence after #6, each triaged: +- `deadeye` 0.832x — the long-registered pre-0.5.4b per-hit shortfall + (pob2_parity.rs); golden did move in the flip but the residual matches the + old ledger. +- `blood-mage` 0.880x / `abyssal-lich` 0.926x — Mageblood mod family + (oracle pins blood-mage's missing `INC CritChance 107 'Mageblood'`; golden + CritChance 72.45→92.1 and CritMult 5.34→5.87 moved with 0.5.4b while PoBR + sits at the old CritMult 5.34 exactly). This is the Phase-1 standalone item. +- `frost-bomb` 0.661x — golden unchanged across the flip; pre-existing + cooldown-DPS gap. +- `smith` TotalDotDPS 0.40x — hit is 0.996x now, so the dot residual is purely + the un-modeled *uptime-scaled* Infernal Cry `DamageGainAsFire` + (fire-source ratio 0.63² ≈ 0.40). Mechanism fully decomposed: uptime = + `min((NumInfernalEmpowers/Speed)/(cooldown + warcryCastTime), 1) × + storedUses` (CalcOffence.lua:3229-3257), `NumInfernalEmpowers = + floor(min(WarcryPower 20, cap 50)/per 10)` (CalcPerform.lua:2117-2131), + gain-as = gem's `infernal_cry_exerted_attack_all_damage_%_to_gain_as_fire_%` + (62 at smith's level) × uptime 19.41% = 12.04. Identical in both vendors — + a pre-existing warcry-uptime gap; needs the warcry buff machinery + (duration/cooldown/cast-time of a non-main skill + WarcryPower config), + registered for its own slice. +- `spirit-walker`/`monk-twister` last ~2%: "Barrage Repeats" MORE DPS + (vendor `output.DpsMultiplier` via `calcLib.mod(..., "DPS")`) — repeat + DPS bonus channel unwired. + Remaining elsewhere, re-triage against fresh `defenceModList` dumps: the wolf-pack EHP remainder decomposed above (Armour 0.98x / ChaosMaxHit 0.87x / Life 1.11x). Also ritualist TotalEHP From 5edb74139ad976bb26039f57faf6f67ba1785021 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 15:41:58 +0800 Subject: [PATCH 36/74] =?UTF-8?q?fix(data):=20honor=20GrantedEffectStatSet?= =?UTF-8?q?s=20RemoveStats=20=E2=80=94=20essence-drain=20WithDotDPS=201.36?= =?UTF-8?q?x=20overestimate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backlog item #7-2 (pre-0.5.4b gap; golden byte-identical across the flip). PoBR overestimated sorceress-chronomancer-essence-drain WithDotDPS by a phantom hit: TotalDPS 120.87 where PoB2 has 0 (WithDotDPS 455.87 vs 335; TotalDotDPS was already exact at 335). Root cause is in the data pipeline, not the calc: GGG's GrantedEffectStatSets carries a RemoveStats column (community dat-schema name: IgnoredStats) listing stats a stat set removes from the base-set merge. PoB2's export zeroes those values (Export/Scripts/skills.lua: 572-597) — Essence Drain's 'Damage over Time' set removes the projectile set's spell_min/max_base_chaos_damage, making it a pure-DoT set (vendor act_int.lua rows {0, 0, dot}). PoBR's pipeline never downloaded the column, so the adapter's additional-set merge kept the concatenated hit damage (62/115 at gem level 14) and the engine produced a hit. Changes: - pipeline/config.json: add IgnoredStats to the GrantedEffectStatSets column list (vendor Export/spec.lua calls it RemoveStats; verified against the community schema validFor=2 table, column 8). Tables re-extracted offline from the cached 4.5.4.3 bundles; all other adapter inputs byte-identical to the previous extraction. - pobr-data-adapter stat_sets.rs: apply the vendor zeroing semantics — for each set, zero the first occurrence of every ignored stat in the (merged) level row; missing column degrades to no-removal (warn-only, consistent with the resilience policy). required_columns registers the new column. - data/4.5.4.3/base/granted_effect_stat_sets.json regenerated: 55 effects change, all of the same shape (DoT/infusion variant sets zeroing the base set's damage stats — e.g. Comet Fire-Infused zeroes base cold, verified line-for-line against vendor act_int.lua). All other regenerated base files byte-identical to the committed ones. - tools/pob2-oracle: skillInfo now reports the selected statSet index/ label and ChaosMin/Max/Dot skillData (used to pin this diagnosis: the build XML selects statSet 2 via the child). Result: essence-drain TotalDPS 120.87 -> 0 (golden 0), WithDotDPS / CombinedDPS 1.36x -> 1.00x. No other parity cell moved. Baselines (honest ratchet): off @5% 59->60, @10% 65->66; dot @5% 16->18, @10% 23->25; panel off 40->41 / 41->42. --- .../pobr-build/tests/parity/ninja_parity.rs | 12 +- .../base/granted_effect_stat_sets.json | 8058 ++++++++--------- pipeline/config.json | 2 +- tools/pob2-oracle/oracle.lua | 13 + .../pobr-data-adapter/src/required_columns.rs | 3 + .../pobr-data-adapter/src/skills/stat_sets.rs | 29 +- 6 files changed, 4080 insertions(+), 4037 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 981e6187..fd413b2a 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -720,8 +720,8 @@ const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refract // act_int.lua:229-231)+ curse 链两缺口(EW 取数等级未吃 +8 spell skill // levels:-58→-66;技能局部 CurseEffect 段缺失:Heightened Curse +25 + // EW 品质 +10 → 敌抗 9→-7,oracle enemyMitigation 逐源钉值)。 -const BASELINE_OFF_HIT5: usize = 59; // frost-bomb 后 59/80(Blazing Critical 58;grenade 短语解禁 56;Communion+Voices 52;迁移基线 39;0.5.0=71) -const BASELINE_OFF_HIT10: usize = 65; // frost-bomb 后 65/80(Blazing Critical 64;grenade 短语解禁 62;Communion+Voices 59;迁移基线 47;0.5.0=74) +const BASELINE_OFF_HIT5: usize = 60; // RemoveStats 后 60/80(frost-bomb 59;essence-drain TotalDPS 0 命中入列)(Blazing Critical 58;grenade 短语解禁 56;Communion+Voices 52;迁移基线 39;0.5.0=71) +const BASELINE_OFF_HIT10: usize = 66; // RemoveStats 后 66/80(frost-bomb 65(Blazing Critical 64;grenade 短语解禁 62;Communion+Voices 59;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -804,8 +804,8 @@ const BASELINE_OFF_HIT10: usize = 65; // frost-bomb 后 65/80(Blazing Critical // TotalDotDPS 1.04x→1.06x 出 @5%(EW 变强的下游——vendor 侧 druid 的 EW // 根本未入 enemy resistMods(curse 槽位/优先级差异,oracle 钉值),PoBR // 施加了它,存量高估被放大 2%,另行追)。@5 净 0、@10 净 +2。 -const BASELINE_DOT_HIT5: usize = 16; // Blazing Critical 后 16/37(grenade 短语解禁 14;Communion+Voices 13;迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 23; // frost-bomb 后 23/37(Blazing Critical 21;grenade 短语解禁 19;Communion+Voices 18;迁移基线 11;0.5.0=28) +const BASELINE_DOT_HIT5: usize = 18; // RemoveStats 后 18/37(essence-drain WithDot/Combined 1.36x→1.00x 双入列;Blazing Critical 16(grenade 短语解禁 14;Communion+Voices 13;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 25; // RemoveStats 后 25/37(frost-bomb 23(Blazing Critical 21;grenade 短语解禁 19;Communion+Voices 18;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 @@ -823,8 +823,8 @@ const BASELINE_DOT_HIT10: usize = 23; // frost-bomb 后 23/37(Blazing Critical /// (template.rs / special_mod.rs)同 commit 全量化——一批 `ModTag::SkillTypes` /// 域词条(Area/Projectile/Grenade 等)在 panel 口径开始正确匹配。effective /// 主口径与防御/进攻/dot 主基线逐值持平(纯 panel 侧收敛)。 -const PANEL_OFF_HIT5: usize = 40; // 0.5.4b #4(Communion+Voices 38;grenade 短语解禁 +2);迁移基线 27;0.5.0=44 -const PANEL_OFF_HIT10: usize = 41; // 0.5.4b #4(Communion+Voices 39;grenade 短语解禁 +2);迁移基线 30;0.5.0=46 +const PANEL_OFF_HIT5: usize = 41; // RemoveStats(essence-drain 幻影击中清零)+1;0.5.4b #4 40(Communion+Voices 38;grenade 短语解禁 +2);迁移基线 27;0.5.0=44 +const PANEL_OFF_HIT10: usize = 42; // RemoveStats +1;0.5.4b #4 41(Communion+Voices 39;grenade 短语解禁 +2);迁移基线 30;0.5.0=46 /// 回归门禁:聚合命中数不得低于已记录基线([`BASELINE_*`])。CI gate,防止改动倒退 parity。 #[test] diff --git a/data/4.5.4.3/base/granted_effect_stat_sets.json b/data/4.5.4.3/base/granted_effect_stat_sets.json index 4bb99be0..557c84f3 100644 --- a/data/4.5.4.3/base/granted_effect_stat_sets.json +++ b/data/4.5.4.3/base/granted_effect_stat_sets.json @@ -1996,11 +1996,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 11.0 + "value": 0.0 } ] }, @@ -2010,11 +2010,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 18.0 + "value": 0.0 } ] }, @@ -2024,11 +2024,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 28.0 + "value": 0.0 } ] }, @@ -2038,11 +2038,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 38.0 + "value": 0.0 } ] }, @@ -2052,11 +2052,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 49.0 + "value": 0.0 } ] }, @@ -2066,11 +2066,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 61.0 + "value": 0.0 } ] }, @@ -2080,11 +2080,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 73.0 + "value": 0.0 } ] }, @@ -2094,11 +2094,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 86.0 + "value": 0.0 } ] }, @@ -2108,11 +2108,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 67.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 100.0 + "value": 0.0 } ] }, @@ -2122,11 +2122,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 114.0 + "value": 0.0 } ] }, @@ -2136,11 +2136,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 86.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 129.0 + "value": 0.0 } ] }, @@ -2150,11 +2150,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 97.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 145.0 + "value": 0.0 } ] }, @@ -2164,11 +2164,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 108.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 161.0 + "value": 0.0 } ] }, @@ -2178,11 +2178,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 119.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 178.0 + "value": 0.0 } ] }, @@ -2192,11 +2192,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 131.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 196.0 + "value": 0.0 } ] }, @@ -2206,11 +2206,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 143.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 214.0 + "value": 0.0 } ] }, @@ -2220,11 +2220,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 156.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 233.0 + "value": 0.0 } ] }, @@ -2234,11 +2234,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 253.0 + "value": 0.0 } ] }, @@ -2248,11 +2248,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 182.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 273.0 + "value": 0.0 } ] }, @@ -2262,11 +2262,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 294.0 + "value": 0.0 } ] }, @@ -2276,11 +2276,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 211.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 316.0 + "value": 0.0 } ] }, @@ -2290,11 +2290,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 226.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 338.0 + "value": 0.0 } ] }, @@ -2304,11 +2304,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 241.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 361.0 + "value": 0.0 } ] }, @@ -2318,11 +2318,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 257.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 385.0 + "value": 0.0 } ] }, @@ -2332,11 +2332,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 273.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 409.0 + "value": 0.0 } ] }, @@ -2346,11 +2346,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 289.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 434.0 + "value": 0.0 } ] }, @@ -2360,11 +2360,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 307.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 460.0 + "value": 0.0 } ] }, @@ -2374,11 +2374,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 324.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 486.0 + "value": 0.0 } ] }, @@ -2388,11 +2388,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 342.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 513.0 + "value": 0.0 } ] }, @@ -2402,11 +2402,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 360.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 541.0 + "value": 0.0 } ] }, @@ -2416,11 +2416,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 379.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 569.0 + "value": 0.0 } ] }, @@ -2430,11 +2430,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 399.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 598.0 + "value": 0.0 } ] }, @@ -2444,11 +2444,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 418.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 628.0 + "value": 0.0 } ] }, @@ -2458,11 +2458,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 439.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 658.0 + "value": 0.0 } ] }, @@ -2472,11 +2472,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 459.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 689.0 + "value": 0.0 } ] }, @@ -2486,11 +2486,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 480.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 720.0 + "value": 0.0 } ] }, @@ -2500,11 +2500,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 502.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 753.0 + "value": 0.0 } ] }, @@ -2514,11 +2514,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 524.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 785.0 + "value": 0.0 } ] }, @@ -2528,11 +2528,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 546.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 819.0 + "value": 0.0 } ] }, @@ -2542,11 +2542,11 @@ "stats": [ { "stat": "main_hand_weapon_minimum_physical_damage", - "value": 569.0 + "value": 0.0 }, { "stat": "main_hand_weapon_maximum_physical_damage", - "value": 853.0 + "value": 0.0 } ] } @@ -16861,11 +16861,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -16882,11 +16882,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -16903,11 +16903,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -16924,11 +16924,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -16945,11 +16945,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -16966,11 +16966,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -16987,11 +16987,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17008,11 +17008,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17029,11 +17029,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17050,11 +17050,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17071,11 +17071,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17092,11 +17092,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17113,11 +17113,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17134,11 +17134,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17155,11 +17155,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17176,11 +17176,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17197,11 +17197,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17218,11 +17218,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 83.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17239,11 +17239,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17260,11 +17260,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 105.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17281,11 +17281,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 118.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17302,11 +17302,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 133.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17323,11 +17323,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 150.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17344,11 +17344,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17365,11 +17365,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 189.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17386,11 +17386,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 213.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17407,11 +17407,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 239.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17428,11 +17428,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 268.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17449,11 +17449,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 301.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17470,11 +17470,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17491,11 +17491,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 380.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17512,11 +17512,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 427.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17533,11 +17533,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 480.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17554,11 +17554,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 539.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17575,11 +17575,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 607.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17596,11 +17596,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 683.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17617,11 +17617,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 769.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17638,11 +17638,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 866.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17659,11 +17659,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 977.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17680,11 +17680,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 58.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1102.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17749,11 +17749,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17770,11 +17770,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17791,11 +17791,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17812,11 +17812,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17833,11 +17833,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17854,11 +17854,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17875,11 +17875,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17896,11 +17896,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17917,11 +17917,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17938,11 +17938,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17959,11 +17959,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -17980,11 +17980,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18001,11 +18001,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18022,11 +18022,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18043,11 +18043,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18064,11 +18064,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18085,11 +18085,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18106,11 +18106,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 83.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18127,11 +18127,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18148,11 +18148,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 105.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18169,11 +18169,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 118.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18190,11 +18190,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 133.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18211,11 +18211,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 150.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18232,11 +18232,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18253,11 +18253,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 189.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18274,11 +18274,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 213.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18295,11 +18295,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 239.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18316,11 +18316,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 268.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18337,11 +18337,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 301.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18358,11 +18358,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18379,11 +18379,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 380.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18400,11 +18400,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 427.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18421,11 +18421,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 480.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18442,11 +18442,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 539.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18463,11 +18463,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 607.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18484,11 +18484,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 683.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18505,11 +18505,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 769.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18526,11 +18526,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 866.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18547,11 +18547,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 977.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -18568,11 +18568,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 58.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1102.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -19267,11 +19267,11 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 5.0 + "value": 0.0 } ] } @@ -23549,7 +23549,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23566,7 +23566,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23583,7 +23583,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23600,7 +23600,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23617,7 +23617,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23634,7 +23634,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23651,7 +23651,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23668,7 +23668,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23685,7 +23685,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23702,7 +23702,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23719,7 +23719,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23736,7 +23736,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23753,7 +23753,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23770,7 +23770,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23787,7 +23787,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23804,7 +23804,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23821,7 +23821,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23838,7 +23838,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23855,7 +23855,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23872,7 +23872,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23889,7 +23889,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23906,7 +23906,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23923,7 +23923,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23940,7 +23940,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23957,7 +23957,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23974,7 +23974,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -23991,7 +23991,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24008,7 +24008,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24025,7 +24025,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24042,7 +24042,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24059,7 +24059,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24076,7 +24076,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24093,7 +24093,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24110,7 +24110,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24127,7 +24127,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24144,7 +24144,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24161,7 +24161,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24178,7 +24178,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24195,7 +24195,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24212,7 +24212,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24261,7 +24261,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24278,7 +24278,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24295,7 +24295,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24312,7 +24312,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24329,7 +24329,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24346,7 +24346,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24363,7 +24363,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24380,7 +24380,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24397,7 +24397,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24414,7 +24414,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24431,7 +24431,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24448,7 +24448,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24465,7 +24465,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24482,7 +24482,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24499,7 +24499,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24516,7 +24516,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24533,7 +24533,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24550,7 +24550,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24567,7 +24567,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24584,7 +24584,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24601,7 +24601,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24618,7 +24618,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24635,7 +24635,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24652,7 +24652,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24669,7 +24669,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24686,7 +24686,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24703,7 +24703,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24720,7 +24720,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24737,7 +24737,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24754,7 +24754,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24771,7 +24771,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24788,7 +24788,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24805,7 +24805,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24822,7 +24822,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24839,7 +24839,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24856,7 +24856,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24873,7 +24873,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24890,7 +24890,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24907,7 +24907,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24924,7 +24924,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -24981,7 +24981,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -24994,7 +24994,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25007,7 +25007,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 1.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25020,7 +25020,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25033,7 +25033,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25046,7 +25046,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 2.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25059,7 +25059,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25072,7 +25072,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25085,7 +25085,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 3.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25098,7 +25098,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25111,7 +25111,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25124,7 +25124,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 4.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25137,7 +25137,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25150,7 +25150,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25163,7 +25163,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 5.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25176,7 +25176,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25189,7 +25189,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25202,7 +25202,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 6.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25215,7 +25215,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25228,7 +25228,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25241,7 +25241,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 7.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25254,7 +25254,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25267,7 +25267,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25280,7 +25280,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 8.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25293,7 +25293,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25306,7 +25306,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25319,7 +25319,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 9.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25332,7 +25332,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25345,7 +25345,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25358,7 +25358,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 10.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25371,7 +25371,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25384,7 +25384,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25397,7 +25397,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 11.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25410,7 +25410,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25423,7 +25423,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25436,7 +25436,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 12.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25449,7 +25449,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25462,7 +25462,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25475,7 +25475,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 13.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -25488,7 +25488,7 @@ "stats": [ { "stat": "volatile_dead_base_number_of_corpses_to_consume", - "value": 14.0 + "value": 0.0 }, { "stat": "base_cold_damage_to_deal_per_minute", @@ -33755,11 +33755,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33776,11 +33776,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33797,11 +33797,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33818,11 +33818,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33839,11 +33839,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33860,11 +33860,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33881,11 +33881,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33902,11 +33902,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33923,11 +33923,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33944,11 +33944,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33965,11 +33965,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -33986,11 +33986,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34007,11 +34007,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 48.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 72.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34028,11 +34028,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 83.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34049,11 +34049,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34070,11 +34070,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34091,11 +34091,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 121.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34112,11 +34112,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 91.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34133,11 +34133,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 103.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 155.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34154,11 +34154,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 116.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 175.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34175,11 +34175,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 131.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 197.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34196,11 +34196,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 148.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 221.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34217,11 +34217,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 166.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 249.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34238,11 +34238,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 187.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 280.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34259,11 +34259,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 210.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 315.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34280,11 +34280,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 236.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 353.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34301,11 +34301,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 264.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 397.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34322,11 +34322,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 445.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34343,11 +34343,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 333.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 500.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34364,11 +34364,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 374.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 562.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34385,11 +34385,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 421.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 631.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34406,11 +34406,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 473.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 709.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34427,11 +34427,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 531.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 797.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34448,11 +34448,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 597.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 896.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34469,11 +34469,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 672.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1008.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34490,11 +34490,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 756.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1134.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34511,11 +34511,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 851.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1277.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34532,11 +34532,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 959.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1439.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34553,11 +34553,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1082.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1623.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34574,11 +34574,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1220.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1831.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -34721,11 +34721,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -47001,11 +47001,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47022,11 +47022,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47043,11 +47043,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47064,11 +47064,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47085,11 +47085,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47106,11 +47106,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 52.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47127,11 +47127,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47148,11 +47148,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47169,11 +47169,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 89.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47190,11 +47190,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 104.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47211,11 +47211,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 122.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47232,11 +47232,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 141.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47253,11 +47253,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 109.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 163.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47274,11 +47274,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 125.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 188.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47295,11 +47295,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 144.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 216.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47316,11 +47316,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 165.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 247.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47337,11 +47337,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 188.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 283.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47358,11 +47358,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 215.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 322.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47379,11 +47379,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 245.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 367.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47400,11 +47400,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 279.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 418.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47421,11 +47421,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 317.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 476.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47442,11 +47442,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 360.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 540.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47463,11 +47463,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 409.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 614.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47484,11 +47484,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 465.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 697.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47505,11 +47505,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 528.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 792.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47526,11 +47526,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 599.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 899.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47547,11 +47547,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 681.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1021.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47568,11 +47568,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 773.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1160.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47589,11 +47589,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 879.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1318.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47610,11 +47610,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 999.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1498.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47631,11 +47631,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1136.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1704.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47652,11 +47652,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1293.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1939.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47673,11 +47673,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1472.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2208.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47694,11 +47694,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1677.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2516.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47715,11 +47715,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1913.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2869.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47736,11 +47736,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2183.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3274.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47757,11 +47757,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2493.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3740.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47778,11 +47778,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2850.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 4275.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47799,11 +47799,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 3261.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 4892.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -47820,11 +47820,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 3735.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 5603.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -48969,11 +48969,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -48998,11 +48998,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49027,11 +49027,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49056,11 +49056,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 56.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 83.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49085,11 +49085,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49114,11 +49114,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 87.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 131.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49143,11 +49143,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 160.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49172,11 +49172,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 128.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 192.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49201,11 +49201,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 153.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 229.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49230,11 +49230,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 180.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 271.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49259,11 +49259,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 212.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 318.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49288,11 +49288,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 248.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 372.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49317,11 +49317,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 289.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 433.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49346,11 +49346,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 335.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 503.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49375,11 +49375,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 388.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 582.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49404,11 +49404,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 448.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 672.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49433,11 +49433,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 517.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 775.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49462,11 +49462,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 595.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 893.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49491,11 +49491,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 685.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1027.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49520,11 +49520,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 787.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1181.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49549,11 +49549,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 905.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1357.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49578,11 +49578,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1039.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1559.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49607,11 +49607,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1193.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1790.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49636,11 +49636,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1371.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2056.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49665,11 +49665,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1574.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2362.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49694,11 +49694,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1809.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2714.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49723,11 +49723,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2080.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3119.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49752,11 +49752,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2392.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3588.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49781,11 +49781,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2753.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 4129.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49810,11 +49810,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3170.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 4756.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49839,11 +49839,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3654.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5481.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49868,11 +49868,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4216.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6324.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49897,11 +49897,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4868.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 7302.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49926,11 +49926,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5627.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 8440.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49955,11 +49955,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6511.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9766.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -49984,11 +49984,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7542.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 11313.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -50013,11 +50013,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 8747.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 13121.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -50042,11 +50042,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 10157.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 15235.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -50071,11 +50071,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 11808.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 17713.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -50100,11 +50100,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 13746.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 20619.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -77238,11 +77238,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77263,11 +77263,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77288,11 +77288,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77313,11 +77313,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77338,11 +77338,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77363,11 +77363,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77388,11 +77388,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77413,11 +77413,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77438,11 +77438,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77463,11 +77463,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77488,11 +77488,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77513,11 +77513,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77538,11 +77538,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77563,11 +77563,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77588,11 +77588,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77613,11 +77613,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77638,11 +77638,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77663,11 +77663,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77688,11 +77688,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77713,11 +77713,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77738,11 +77738,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77763,11 +77763,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77788,11 +77788,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77813,11 +77813,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 48.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77838,11 +77838,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 80.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77863,11 +77863,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 89.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77888,11 +77888,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77913,11 +77913,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 110.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77938,11 +77938,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 123.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77963,11 +77963,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 91.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -77988,11 +77988,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 153.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78013,11 +78013,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 170.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78038,11 +78038,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78063,11 +78063,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 141.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 212.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78088,11 +78088,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 157.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 236.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78113,11 +78113,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 176.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 264.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78138,11 +78138,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 294.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78163,11 +78163,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 219.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 329.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78188,11 +78188,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 245.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 367.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -78213,11 +78213,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 274.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 410.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -79375,7 +79375,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79385,7 +79385,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79395,7 +79395,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79405,7 +79405,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79415,7 +79415,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79425,7 +79425,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79435,7 +79435,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] }, @@ -79445,7 +79445,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79455,7 +79455,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79465,7 +79465,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79475,7 +79475,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79485,7 +79485,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79495,7 +79495,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79505,7 +79505,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79515,7 +79515,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 2.0 + "value": 0.0 } ] }, @@ -79525,7 +79525,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79535,7 +79535,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79545,7 +79545,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79555,7 +79555,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79565,7 +79565,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79575,7 +79575,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79585,7 +79585,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79595,7 +79595,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79605,7 +79605,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79615,7 +79615,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79625,7 +79625,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79635,7 +79635,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79645,7 +79645,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79655,7 +79655,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79665,7 +79665,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79675,7 +79675,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79685,7 +79685,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79695,7 +79695,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79705,7 +79705,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79715,7 +79715,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79725,7 +79725,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79735,7 +79735,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79745,7 +79745,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79755,7 +79755,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] }, @@ -79765,7 +79765,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 3.0 + "value": 0.0 } ] } @@ -79874,7 +79874,7 @@ "stats": [ { "stat": "consume_parry_debuff_on_hit_to_gain_X_frenzy_charges", - "value": 1.0 + "value": 0.0 } ] } @@ -98640,11 +98640,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98665,11 +98665,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98690,11 +98690,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98715,11 +98715,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98740,11 +98740,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98765,11 +98765,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98790,11 +98790,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98815,11 +98815,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 52.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98840,11 +98840,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98865,11 +98865,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98890,11 +98890,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98915,11 +98915,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98940,11 +98940,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98965,11 +98965,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 132.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -98990,11 +98990,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 101.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 151.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99015,11 +99015,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99040,11 +99040,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 132.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 198.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99065,11 +99065,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 150.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 226.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99090,11 +99090,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 257.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99115,11 +99115,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 195.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 293.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99140,11 +99140,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 222.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 333.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99165,11 +99165,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 252.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 378.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99190,11 +99190,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 287.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 430.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99215,11 +99215,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 325.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 488.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99240,11 +99240,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 370.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 554.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99265,11 +99265,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 420.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 629.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99290,11 +99290,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 477.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 715.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99315,11 +99315,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 541.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 812.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99340,11 +99340,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 615.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 923.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99365,11 +99365,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 699.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1049.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99390,11 +99390,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 795.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1193.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99415,11 +99415,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 905.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1357.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99440,11 +99440,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1030.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1546.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99465,11 +99465,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1174.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1761.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99490,11 +99490,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1339.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2008.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99515,11 +99515,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1528.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2292.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99540,11 +99540,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1745.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2618.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99565,11 +99565,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1995.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2993.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99590,11 +99590,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2283.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3424.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -99615,11 +99615,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2615.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3922.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -103182,11 +103182,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103207,11 +103207,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103232,11 +103232,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103257,11 +103257,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103282,11 +103282,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103307,11 +103307,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103332,11 +103332,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103357,11 +103357,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103382,11 +103382,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103407,11 +103407,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103432,11 +103432,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103457,11 +103457,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103482,11 +103482,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 65.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103507,11 +103507,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103532,11 +103532,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 58.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 86.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103557,11 +103557,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103582,11 +103582,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 113.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103607,11 +103607,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 86.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103632,11 +103632,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 98.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 147.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103657,11 +103657,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 111.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 167.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103682,11 +103682,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103707,11 +103707,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 144.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 216.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103732,11 +103732,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 164.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 246.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103757,11 +103757,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 186.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 279.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103782,11 +103782,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 211.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 317.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103807,11 +103807,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 240.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 360.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103832,11 +103832,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 272.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 408.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103857,11 +103857,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 309.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 464.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103882,11 +103882,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 351.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 527.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103907,11 +103907,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 400.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 599.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103932,11 +103932,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 454.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 682.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103957,11 +103957,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 517.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 776.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -103982,11 +103982,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 589.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 883.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104007,11 +104007,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 671.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1006.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104032,11 +104032,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 765.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1148.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104057,11 +104057,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 873.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1310.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104082,11 +104082,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 997.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1496.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104107,11 +104107,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1140.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1710.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104132,11 +104132,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1305.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1957.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -104157,11 +104157,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1494.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2241.0 + "value": 0.0 }, { "stat": "base_number_of_entangle_roots_allowed", @@ -105856,11 +105856,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105873,11 +105873,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105890,11 +105890,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105907,11 +105907,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105924,11 +105924,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105941,11 +105941,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105958,11 +105958,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105975,11 +105975,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -105992,11 +105992,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106009,11 +106009,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106026,11 +106026,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106043,11 +106043,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 87.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106060,11 +106060,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 100.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106077,11 +106077,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106094,11 +106094,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 133.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106111,11 +106111,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 152.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106128,11 +106128,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106145,11 +106145,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 198.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106162,11 +106162,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 121.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 226.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106179,11 +106179,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 138.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 257.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106196,11 +106196,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 157.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 292.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106213,11 +106213,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 179.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 332.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106230,11 +106230,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 203.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 377.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106247,11 +106247,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 230.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 428.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106264,11 +106264,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 262.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 486.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106281,11 +106281,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 552.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106298,11 +106298,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 627.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106315,11 +106315,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 383.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 712.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106332,11 +106332,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 436.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 809.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106349,11 +106349,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 495.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 920.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106366,11 +106366,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 563.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1046.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106383,11 +106383,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 641.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1190.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106400,11 +106400,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 730.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1355.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106417,11 +106417,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 832.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1545.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106434,11 +106434,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 948.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1761.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106451,11 +106451,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1082.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 2010.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106468,11 +106468,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1236.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 2296.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106485,11 +106485,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1413.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 2624.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106502,11 +106502,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1617.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 3003.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106519,11 +106519,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1852.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 3440.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -106638,11 +106638,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -112256,7 +112256,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 40.0 + "value": 0.0 } ] }, @@ -112270,7 +112270,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 41.0 + "value": 0.0 } ] }, @@ -112284,7 +112284,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 42.0 + "value": 0.0 } ] }, @@ -112298,7 +112298,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 43.0 + "value": 0.0 } ] }, @@ -112312,7 +112312,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 44.0 + "value": 0.0 } ] }, @@ -112326,7 +112326,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 45.0 + "value": 0.0 } ] }, @@ -112340,7 +112340,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 46.0 + "value": 0.0 } ] }, @@ -112354,7 +112354,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 47.0 + "value": 0.0 } ] }, @@ -112368,7 +112368,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 48.0 + "value": 0.0 } ] }, @@ -112382,7 +112382,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 49.0 + "value": 0.0 } ] }, @@ -112396,7 +112396,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 50.0 + "value": 0.0 } ] }, @@ -112410,7 +112410,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 51.0 + "value": 0.0 } ] }, @@ -112424,7 +112424,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 52.0 + "value": 0.0 } ] }, @@ -112438,7 +112438,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 53.0 + "value": 0.0 } ] }, @@ -112452,7 +112452,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 54.0 + "value": 0.0 } ] }, @@ -112466,7 +112466,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 55.0 + "value": 0.0 } ] }, @@ -112480,7 +112480,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 56.0 + "value": 0.0 } ] }, @@ -112494,7 +112494,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 57.0 + "value": 0.0 } ] }, @@ -112508,7 +112508,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 58.0 + "value": 0.0 } ] }, @@ -112522,7 +112522,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 59.0 + "value": 0.0 } ] }, @@ -112536,7 +112536,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 60.0 + "value": 0.0 } ] }, @@ -112550,7 +112550,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 61.0 + "value": 0.0 } ] }, @@ -112564,7 +112564,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 62.0 + "value": 0.0 } ] }, @@ -112578,7 +112578,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 63.0 + "value": 0.0 } ] }, @@ -112592,7 +112592,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 64.0 + "value": 0.0 } ] }, @@ -112606,7 +112606,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 65.0 + "value": 0.0 } ] }, @@ -112620,7 +112620,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 66.0 + "value": 0.0 } ] }, @@ -112634,7 +112634,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 67.0 + "value": 0.0 } ] }, @@ -112648,7 +112648,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 68.0 + "value": 0.0 } ] }, @@ -112662,7 +112662,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 69.0 + "value": 0.0 } ] }, @@ -112676,7 +112676,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 70.0 + "value": 0.0 } ] }, @@ -112690,7 +112690,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 71.0 + "value": 0.0 } ] }, @@ -112704,7 +112704,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 72.0 + "value": 0.0 } ] }, @@ -112718,7 +112718,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 73.0 + "value": 0.0 } ] }, @@ -112732,7 +112732,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 74.0 + "value": 0.0 } ] }, @@ -112746,7 +112746,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 75.0 + "value": 0.0 } ] }, @@ -112760,7 +112760,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 76.0 + "value": 0.0 } ] }, @@ -112774,7 +112774,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 77.0 + "value": 0.0 } ] }, @@ -112788,7 +112788,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 78.0 + "value": 0.0 } ] }, @@ -112802,7 +112802,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 79.0 + "value": 0.0 } ] } @@ -112965,7 +112965,7 @@ }, { "stat": "active_skill_base_area_of_effect_radius", - "value": 40.0 + "value": 0.0 } ] } @@ -117288,11 +117288,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 6.0 + "value": 0.0 } ] } @@ -117337,11 +117337,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -117362,11 +117362,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 4883.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 7324.0 + "value": 0.0 }, { "stat": "fire_djinn_flame_rune_tick_rate_ms", @@ -118602,11 +118602,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118623,11 +118623,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118644,11 +118644,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118665,11 +118665,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118686,11 +118686,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118707,11 +118707,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118728,11 +118728,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118749,11 +118749,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118770,11 +118770,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118791,11 +118791,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118812,11 +118812,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 68.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118833,11 +118833,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 79.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 118.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118854,11 +118854,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 91.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 136.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118875,11 +118875,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 104.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 156.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118896,11 +118896,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 119.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 178.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118917,11 +118917,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 135.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 203.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118938,11 +118938,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 154.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 231.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118959,11 +118959,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 174.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 262.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -118980,11 +118980,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 198.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119001,11 +119001,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 224.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 336.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119022,11 +119022,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 253.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 379.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119043,11 +119043,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 286.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 429.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119064,11 +119064,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 323.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 484.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119085,11 +119085,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 364.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 546.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119106,11 +119106,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 411.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 616.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119127,11 +119127,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 463.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 695.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119148,11 +119148,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 522.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 784.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119169,11 +119169,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 589.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 884.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119190,11 +119190,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 665.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 997.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119211,11 +119211,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 750.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1125.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119232,11 +119232,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 847.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1270.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119253,11 +119253,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 956.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1434.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119274,11 +119274,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1080.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1619.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119295,11 +119295,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1220.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1830.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119316,11 +119316,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1380.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2070.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119337,11 +119337,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1561.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2342.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119358,11 +119358,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1768.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2651.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119379,11 +119379,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2003.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3004.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119400,11 +119400,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2271.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3407.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -119421,11 +119421,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2577.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3866.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -121303,11 +121303,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121324,11 +121324,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121345,11 +121345,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121366,11 +121366,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121387,11 +121387,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121408,11 +121408,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121429,11 +121429,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121450,11 +121450,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121471,11 +121471,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121492,11 +121492,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121513,11 +121513,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121534,11 +121534,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121555,11 +121555,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121576,11 +121576,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121597,11 +121597,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 56.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 84.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121618,11 +121618,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 65.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 97.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121639,11 +121639,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 111.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121660,11 +121660,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121681,11 +121681,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 97.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 146.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121702,11 +121702,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 111.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 167.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121723,11 +121723,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 191.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121744,11 +121744,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 145.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 218.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121765,11 +121765,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 166.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 249.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121786,11 +121786,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 284.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121807,11 +121807,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 217.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 325.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121828,11 +121828,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 247.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 371.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121849,11 +121849,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 283.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 424.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121870,11 +121870,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 323.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 485.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121891,11 +121891,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 369.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 554.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121912,11 +121912,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 423.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 634.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121933,11 +121933,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 484.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 726.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121954,11 +121954,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 555.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 832.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121975,11 +121975,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 636.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 954.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -121996,11 +121996,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 730.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1095.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122017,11 +122017,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 838.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1257.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122038,11 +122038,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 964.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1446.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122059,11 +122059,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1109.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1664.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122080,11 +122080,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1278.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1917.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122101,11 +122101,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1474.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2211.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122122,11 +122122,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1702.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2553.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -122207,11 +122207,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122228,11 +122228,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122249,11 +122249,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122270,11 +122270,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122291,11 +122291,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122312,11 +122312,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122333,11 +122333,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122354,11 +122354,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122375,11 +122375,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122396,11 +122396,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122417,11 +122417,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122438,11 +122438,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122459,11 +122459,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122480,11 +122480,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122501,11 +122501,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 56.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 84.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122522,11 +122522,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 65.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 97.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122543,11 +122543,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 111.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122564,11 +122564,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122585,11 +122585,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 97.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 146.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122606,11 +122606,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 111.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 167.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122627,11 +122627,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 191.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122648,11 +122648,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 145.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 218.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122669,11 +122669,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 166.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 249.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122690,11 +122690,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 284.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122711,11 +122711,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 217.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 325.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122732,11 +122732,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 247.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 371.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122753,11 +122753,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 283.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 424.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122774,11 +122774,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 323.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 485.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122795,11 +122795,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 369.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 554.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122816,11 +122816,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 423.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 634.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122837,11 +122837,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 484.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 726.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122858,11 +122858,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 555.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 832.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122879,11 +122879,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 636.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 954.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122900,11 +122900,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 730.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1095.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122921,11 +122921,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 838.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1257.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122942,11 +122942,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 964.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1446.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122963,11 +122963,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1109.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1664.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -122984,11 +122984,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1278.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1917.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -123005,11 +123005,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1474.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2211.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -123026,11 +123026,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1702.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2553.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -123189,11 +123189,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -123274,11 +123274,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -125023,15 +125023,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 105.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125039,7 +125039,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4500.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125064,15 +125064,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 141.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125080,7 +125080,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4600.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125105,15 +125105,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 193.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125121,7 +125121,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4700.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125146,15 +125146,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 253.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125162,7 +125162,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4800.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125187,15 +125187,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 320.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125203,7 +125203,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4900.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125228,15 +125228,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 396.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125244,7 +125244,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5000.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125269,15 +125269,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 37.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 483.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125285,7 +125285,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5100.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125310,15 +125310,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 581.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125326,7 +125326,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5200.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125351,15 +125351,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 52.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 692.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125367,7 +125367,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5300.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125392,15 +125392,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 819.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125408,7 +125408,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5400.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125433,15 +125433,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 962.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125449,7 +125449,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5500.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125474,15 +125474,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1126.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125490,7 +125490,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5600.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125515,15 +125515,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1311.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125531,7 +125531,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5700.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125556,15 +125556,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 77.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1522.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125572,7 +125572,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5800.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125597,15 +125597,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 89.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 134.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1762.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125613,7 +125613,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5900.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125638,15 +125638,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 103.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 154.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2035.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125654,7 +125654,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6000.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125679,15 +125679,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 119.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 178.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2347.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125695,7 +125695,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6100.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125720,15 +125720,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 205.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2703.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125736,7 +125736,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6200.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125761,15 +125761,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 157.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 236.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3110.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125777,7 +125777,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6300.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125802,15 +125802,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 181.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 271.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3575.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125818,7 +125818,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6400.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125843,15 +125843,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 208.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 311.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 4108.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125859,7 +125859,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6500.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125884,15 +125884,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 238.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 358.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 4718.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125900,7 +125900,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6600.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125925,15 +125925,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 274.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 411.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 5419.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125941,7 +125941,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6700.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -125966,15 +125966,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 314.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 472.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 6223.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -125982,7 +125982,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6800.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126007,15 +126007,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 361.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 542.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 7149.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126023,7 +126023,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6900.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126048,15 +126048,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 415.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 623.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 8214.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126064,7 +126064,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7000.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126089,15 +126089,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 477.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 716.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 9443.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126105,7 +126105,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7100.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126130,15 +126130,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 549.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 823.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 10861.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126146,7 +126146,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7200.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126171,15 +126171,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 632.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 947.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 12499.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126187,7 +126187,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7300.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126212,15 +126212,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 727.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1091.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 14396.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126228,7 +126228,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7400.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126253,15 +126253,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 838.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1258.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 16593.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126269,7 +126269,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7450.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126294,15 +126294,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 967.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1451.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 19142.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126310,7 +126310,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7500.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126335,15 +126335,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1117.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1675.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 22104.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126351,7 +126351,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7550.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126376,15 +126376,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1291.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1936.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 25550.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126392,7 +126392,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7600.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126417,15 +126417,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1494.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2241.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 29564.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126433,7 +126433,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7650.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126458,15 +126458,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1730.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2596.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 34247.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126474,7 +126474,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7700.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126499,15 +126499,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2007.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3010.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 39718.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126515,7 +126515,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7750.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126540,15 +126540,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2330.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3495.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 46118.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126556,7 +126556,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7800.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126581,15 +126581,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2709.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 4064.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 53617.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126597,7 +126597,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7850.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126622,15 +126622,15 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 3154.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 4730.0 + "value": 0.0 }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 62416.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126638,7 +126638,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7900.0 + "value": 0.0 }, { "stat": "flame_wall_minimum_added_fire_damage", @@ -126707,7 +126707,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 105.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126715,7 +126715,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4500.0 + "value": 0.0 } ] }, @@ -126732,7 +126732,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 141.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126740,7 +126740,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4600.0 + "value": 0.0 } ] }, @@ -126757,7 +126757,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 193.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126765,7 +126765,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4700.0 + "value": 0.0 } ] }, @@ -126782,7 +126782,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 253.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126790,7 +126790,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4800.0 + "value": 0.0 } ] }, @@ -126807,7 +126807,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 320.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126815,7 +126815,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 4900.0 + "value": 0.0 } ] }, @@ -126832,7 +126832,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 396.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126840,7 +126840,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5000.0 + "value": 0.0 } ] }, @@ -126857,7 +126857,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 483.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126865,7 +126865,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5100.0 + "value": 0.0 } ] }, @@ -126882,7 +126882,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 581.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126890,7 +126890,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5200.0 + "value": 0.0 } ] }, @@ -126907,7 +126907,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 692.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126915,7 +126915,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5300.0 + "value": 0.0 } ] }, @@ -126932,7 +126932,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 819.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126940,7 +126940,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5400.0 + "value": 0.0 } ] }, @@ -126957,7 +126957,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 962.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126965,7 +126965,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5500.0 + "value": 0.0 } ] }, @@ -126982,7 +126982,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1126.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -126990,7 +126990,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5600.0 + "value": 0.0 } ] }, @@ -127007,7 +127007,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1311.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127015,7 +127015,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5700.0 + "value": 0.0 } ] }, @@ -127032,7 +127032,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1522.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127040,7 +127040,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5800.0 + "value": 0.0 } ] }, @@ -127057,7 +127057,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1762.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127065,7 +127065,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 5900.0 + "value": 0.0 } ] }, @@ -127082,7 +127082,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2035.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127090,7 +127090,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6000.0 + "value": 0.0 } ] }, @@ -127107,7 +127107,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2347.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127115,7 +127115,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6100.0 + "value": 0.0 } ] }, @@ -127132,7 +127132,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2703.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127140,7 +127140,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6200.0 + "value": 0.0 } ] }, @@ -127157,7 +127157,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3110.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127165,7 +127165,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6300.0 + "value": 0.0 } ] }, @@ -127182,7 +127182,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3575.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127190,7 +127190,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6400.0 + "value": 0.0 } ] }, @@ -127207,7 +127207,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 4108.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127215,7 +127215,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6500.0 + "value": 0.0 } ] }, @@ -127232,7 +127232,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 4718.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127240,7 +127240,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6600.0 + "value": 0.0 } ] }, @@ -127257,7 +127257,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 5419.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127265,7 +127265,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6700.0 + "value": 0.0 } ] }, @@ -127282,7 +127282,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 6223.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127290,7 +127290,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6800.0 + "value": 0.0 } ] }, @@ -127307,7 +127307,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 7149.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127315,7 +127315,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 6900.0 + "value": 0.0 } ] }, @@ -127332,7 +127332,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 8214.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127340,7 +127340,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7000.0 + "value": 0.0 } ] }, @@ -127357,7 +127357,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 9443.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127365,7 +127365,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7100.0 + "value": 0.0 } ] }, @@ -127382,7 +127382,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 10861.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127390,7 +127390,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7200.0 + "value": 0.0 } ] }, @@ -127407,7 +127407,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 12499.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127415,7 +127415,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7300.0 + "value": 0.0 } ] }, @@ -127432,7 +127432,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 14396.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127440,7 +127440,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7400.0 + "value": 0.0 } ] }, @@ -127457,7 +127457,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 16593.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127465,7 +127465,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7450.0 + "value": 0.0 } ] }, @@ -127482,7 +127482,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 19142.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127490,7 +127490,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7500.0 + "value": 0.0 } ] }, @@ -127507,7 +127507,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 22104.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127515,7 +127515,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7550.0 + "value": 0.0 } ] }, @@ -127532,7 +127532,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 25550.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127540,7 +127540,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7600.0 + "value": 0.0 } ] }, @@ -127557,7 +127557,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 29564.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127565,7 +127565,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7650.0 + "value": 0.0 } ] }, @@ -127582,7 +127582,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 34247.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127590,7 +127590,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7700.0 + "value": 0.0 } ] }, @@ -127607,7 +127607,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 39718.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127615,7 +127615,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7750.0 + "value": 0.0 } ] }, @@ -127632,7 +127632,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 46118.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127640,7 +127640,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7800.0 + "value": 0.0 } ] }, @@ -127657,7 +127657,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 53617.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127665,7 +127665,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7850.0 + "value": 0.0 } ] }, @@ -127682,7 +127682,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 62416.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -127690,7 +127690,7 @@ }, { "stat": "base_skill_effect_duration", - "value": 7900.0 + "value": 0.0 } ] } @@ -132609,7 +132609,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132619,7 +132619,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132629,7 +132629,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132639,7 +132639,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132649,7 +132649,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132659,7 +132659,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132669,7 +132669,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] }, @@ -132679,7 +132679,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132689,7 +132689,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132699,7 +132699,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132709,7 +132709,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132719,7 +132719,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132729,7 +132729,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132739,7 +132739,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132749,7 +132749,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 3.0 + "value": 0.0 } ] }, @@ -132759,7 +132759,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132769,7 +132769,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132779,7 +132779,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132789,7 +132789,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132799,7 +132799,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132809,7 +132809,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132819,7 +132819,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132829,7 +132829,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 4.0 + "value": 0.0 } ] }, @@ -132839,7 +132839,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132849,7 +132849,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132859,7 +132859,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132869,7 +132869,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132879,7 +132879,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132889,7 +132889,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132899,7 +132899,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132909,7 +132909,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132919,7 +132919,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132929,7 +132929,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132939,7 +132939,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132949,7 +132949,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132959,7 +132959,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132969,7 +132969,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132979,7 +132979,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132989,7 +132989,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] }, @@ -132999,7 +132999,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 5.0 + "value": 0.0 } ] } @@ -133184,7 +133184,7 @@ "stats": [ { "stat": "projectile_base_number_of_targets_to_pierce", - "value": 2.0 + "value": 0.0 } ] } @@ -137574,11 +137574,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137595,11 +137595,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137616,11 +137616,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137637,11 +137637,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137658,11 +137658,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137679,11 +137679,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137700,11 +137700,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137721,11 +137721,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137742,11 +137742,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137763,11 +137763,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137784,11 +137784,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137805,11 +137805,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137826,11 +137826,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137847,11 +137847,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137868,11 +137868,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137889,11 +137889,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137910,11 +137910,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137931,11 +137931,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137952,11 +137952,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137973,11 +137973,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 68.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -137994,11 +137994,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138015,11 +138015,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 86.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138036,11 +138036,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 96.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138057,11 +138057,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 72.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 108.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138078,11 +138078,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 122.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138099,11 +138099,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 91.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138120,11 +138120,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 154.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138141,11 +138141,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138162,11 +138162,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138183,11 +138183,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 145.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 218.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138204,11 +138204,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 163.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 244.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138225,11 +138225,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 183.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 275.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138246,11 +138246,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 206.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 309.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138267,11 +138267,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 231.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 347.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138288,11 +138288,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 260.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 390.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138309,11 +138309,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 293.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 439.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138330,11 +138330,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 330.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 495.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138351,11 +138351,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 372.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 557.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138372,11 +138372,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 419.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 628.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138393,11 +138393,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 473.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 709.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138454,11 +138454,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138475,11 +138475,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138496,11 +138496,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138517,11 +138517,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138538,11 +138538,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138559,11 +138559,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138580,11 +138580,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138601,11 +138601,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138622,11 +138622,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138643,11 +138643,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138664,11 +138664,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138685,11 +138685,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138706,11 +138706,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138727,11 +138727,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138748,11 +138748,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138769,11 +138769,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138790,11 +138790,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138811,11 +138811,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138832,11 +138832,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138853,11 +138853,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 68.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138874,11 +138874,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138895,11 +138895,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 86.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138916,11 +138916,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 96.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138937,11 +138937,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 72.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 108.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138958,11 +138958,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 122.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -138979,11 +138979,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 91.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139000,11 +139000,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 154.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139021,11 +139021,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139042,11 +139042,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139063,11 +139063,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 145.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 218.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139084,11 +139084,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 163.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 244.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139105,11 +139105,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 183.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 275.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139126,11 +139126,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 206.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 309.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139147,11 +139147,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 231.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 347.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139168,11 +139168,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 260.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 390.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139189,11 +139189,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 293.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 439.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139210,11 +139210,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 330.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 495.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139231,11 +139231,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 372.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 557.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139252,11 +139252,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 419.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 628.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -139273,11 +139273,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 473.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 709.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -140255,11 +140255,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140284,11 +140284,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140313,11 +140313,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140342,11 +140342,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140371,11 +140371,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140400,11 +140400,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140429,11 +140429,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140458,11 +140458,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 37.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140487,11 +140487,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 65.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140516,11 +140516,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140545,11 +140545,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140574,11 +140574,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 67.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 101.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140603,11 +140603,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 77.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 116.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140632,11 +140632,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 133.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140661,11 +140661,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 101.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 151.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140690,11 +140690,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 172.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140719,11 +140719,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 130.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 195.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140748,11 +140748,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 147.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 220.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140777,11 +140777,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 166.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 249.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140806,11 +140806,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 187.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 280.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140835,11 +140835,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 211.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 316.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140864,11 +140864,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 237.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 356.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140893,11 +140893,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 267.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 400.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140922,11 +140922,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 300.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 449.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140951,11 +140951,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 337.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 505.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -140980,11 +140980,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 378.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 567.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141009,11 +141009,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 425.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 637.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141038,11 +141038,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 477.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 715.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141067,11 +141067,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 535.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 803.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141096,11 +141096,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 601.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 902.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141125,11 +141125,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 675.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1013.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141154,11 +141154,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 759.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1138.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141183,11 +141183,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 853.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1279.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141212,11 +141212,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 959.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1438.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141241,11 +141241,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1079.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1618.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141270,11 +141270,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1214.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1821.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141299,11 +141299,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1367.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2051.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141328,11 +141328,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1540.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2311.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141357,11 +141357,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1737.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2605.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -141386,11 +141386,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1960.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2939.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142384,11 +142384,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142405,11 +142405,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142434,11 +142434,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142463,11 +142463,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142492,11 +142492,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142521,11 +142521,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 37.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142550,11 +142550,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142579,11 +142579,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142608,11 +142608,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 37.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142637,11 +142637,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142666,11 +142666,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 68.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142695,11 +142695,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142724,11 +142724,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142753,11 +142753,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 90.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142782,11 +142782,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 65.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 98.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142811,11 +142811,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142840,11 +142840,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142869,11 +142869,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 123.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142898,11 +142898,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 132.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142927,11 +142927,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 141.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142956,11 +142956,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 100.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 151.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -142985,11 +142985,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 161.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143014,11 +143014,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143043,11 +143043,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 121.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 182.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143072,11 +143072,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 193.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143101,11 +143101,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 136.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 204.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143130,11 +143130,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 144.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 216.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143159,11 +143159,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 152.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 229.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143188,11 +143188,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 161.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 241.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143217,11 +143217,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 170.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 255.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143246,11 +143246,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 179.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 268.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143275,11 +143275,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 188.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 282.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143304,11 +143304,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 198.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143333,11 +143333,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 208.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 312.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143362,11 +143362,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 218.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 328.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143391,11 +143391,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 229.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 344.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143420,11 +143420,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 240.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 361.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143449,11 +143449,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 252.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 378.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143478,11 +143478,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 264.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 396.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -143507,11 +143507,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 276.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 414.0 + "value": 0.0 }, { "stat": "wall_maximum_length", @@ -144273,11 +144273,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144298,11 +144298,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144323,11 +144323,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144348,11 +144348,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144373,11 +144373,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144398,11 +144398,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144423,11 +144423,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144448,11 +144448,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144473,11 +144473,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 48.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144498,11 +144498,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 56.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144523,11 +144523,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144548,11 +144548,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144573,11 +144573,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 58.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 87.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144598,11 +144598,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 67.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 100.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144623,11 +144623,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144648,11 +144648,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 87.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 130.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144673,11 +144673,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 148.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144698,11 +144698,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 168.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144723,11 +144723,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144748,11 +144748,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 144.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 216.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144773,11 +144773,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 162.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 244.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144798,11 +144798,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 184.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 275.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144823,11 +144823,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 207.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 311.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144848,11 +144848,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 234.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 351.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144873,11 +144873,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 264.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 396.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144898,11 +144898,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 298.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 446.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144923,11 +144923,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 336.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 503.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144948,11 +144948,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 379.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 568.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144973,11 +144973,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 427.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 640.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -144998,11 +144998,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 482.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 723.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145023,11 +145023,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 544.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 816.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145048,11 +145048,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 614.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 921.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145073,11 +145073,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 693.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1040.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145098,11 +145098,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 784.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1176.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145123,11 +145123,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 886.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1329.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145148,11 +145148,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1003.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1504.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145173,11 +145173,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1135.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1703.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145198,11 +145198,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1286.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1930.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145223,11 +145223,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1459.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2188.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145248,11 +145248,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1655.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2483.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -145355,11 +145355,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "active_skill_chill_effect_+%_final", @@ -207588,11 +207588,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207617,11 +207617,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207646,11 +207646,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207675,11 +207675,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207704,11 +207704,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207733,11 +207733,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207762,11 +207762,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207791,11 +207791,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207820,11 +207820,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207849,11 +207849,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207878,11 +207878,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207907,11 +207907,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207936,11 +207936,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 56.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207965,11 +207965,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 65.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -207994,11 +207994,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208023,11 +208023,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 58.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 86.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208052,11 +208052,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208081,11 +208081,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208110,11 +208110,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 87.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 130.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208139,11 +208139,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208168,11 +208168,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 170.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208197,11 +208197,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 130.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 195.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208226,11 +208226,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 148.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 222.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208255,11 +208255,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 254.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208284,11 +208284,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 193.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 290.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208313,11 +208313,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 221.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 331.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208342,11 +208342,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 252.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 379.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208371,11 +208371,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 288.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 433.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208400,11 +208400,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 330.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 495.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208429,11 +208429,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 377.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 566.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208458,11 +208458,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 432.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 648.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208487,11 +208487,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 495.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 743.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208516,11 +208516,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 568.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 852.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208545,11 +208545,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 652.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 977.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208574,11 +208574,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 749.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1123.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208603,11 +208603,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 861.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1291.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208632,11 +208632,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 991.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1486.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208661,11 +208661,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1141.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1712.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208690,11 +208690,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1316.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1974.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -208719,11 +208719,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1520.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2280.0 + "value": 0.0 }, { "stat": "frost_wall_maximum_life", @@ -221050,7 +221050,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 3000.0 + "value": 0.0 } ] }, @@ -221060,7 +221060,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2950.0 + "value": 0.0 } ] }, @@ -221070,7 +221070,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2900.0 + "value": 0.0 } ] }, @@ -221080,7 +221080,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2850.0 + "value": 0.0 } ] }, @@ -221090,7 +221090,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2800.0 + "value": 0.0 } ] }, @@ -221100,7 +221100,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2750.0 + "value": 0.0 } ] }, @@ -221110,7 +221110,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2700.0 + "value": 0.0 } ] }, @@ -221120,7 +221120,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2650.0 + "value": 0.0 } ] }, @@ -221130,7 +221130,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2600.0 + "value": 0.0 } ] }, @@ -221140,7 +221140,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2550.0 + "value": 0.0 } ] }, @@ -221150,7 +221150,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2500.0 + "value": 0.0 } ] }, @@ -221160,7 +221160,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2450.0 + "value": 0.0 } ] }, @@ -221170,7 +221170,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2400.0 + "value": 0.0 } ] }, @@ -221180,7 +221180,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2350.0 + "value": 0.0 } ] }, @@ -221190,7 +221190,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2300.0 + "value": 0.0 } ] }, @@ -221200,7 +221200,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2250.0 + "value": 0.0 } ] }, @@ -221210,7 +221210,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2200.0 + "value": 0.0 } ] }, @@ -221220,7 +221220,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2150.0 + "value": 0.0 } ] }, @@ -221230,7 +221230,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2100.0 + "value": 0.0 } ] }, @@ -221240,7 +221240,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2050.0 + "value": 0.0 } ] }, @@ -221250,7 +221250,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2050.0 + "value": 0.0 } ] }, @@ -221260,7 +221260,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2000.0 + "value": 0.0 } ] }, @@ -221270,7 +221270,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 2000.0 + "value": 0.0 } ] }, @@ -221280,7 +221280,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1950.0 + "value": 0.0 } ] }, @@ -221290,7 +221290,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1950.0 + "value": 0.0 } ] }, @@ -221300,7 +221300,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1900.0 + "value": 0.0 } ] }, @@ -221310,7 +221310,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1900.0 + "value": 0.0 } ] }, @@ -221320,7 +221320,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1850.0 + "value": 0.0 } ] }, @@ -221330,7 +221330,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1850.0 + "value": 0.0 } ] }, @@ -221340,7 +221340,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1800.0 + "value": 0.0 } ] }, @@ -221350,7 +221350,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1800.0 + "value": 0.0 } ] }, @@ -221360,7 +221360,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1750.0 + "value": 0.0 } ] }, @@ -221370,7 +221370,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1750.0 + "value": 0.0 } ] }, @@ -221380,7 +221380,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1700.0 + "value": 0.0 } ] }, @@ -221390,7 +221390,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1700.0 + "value": 0.0 } ] }, @@ -221400,7 +221400,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1700.0 + "value": 0.0 } ] }, @@ -221410,7 +221410,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1650.0 + "value": 0.0 } ] }, @@ -221420,7 +221420,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1650.0 + "value": 0.0 } ] }, @@ -221430,7 +221430,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1600.0 + "value": 0.0 } ] }, @@ -221440,7 +221440,7 @@ "stats": [ { "stat": "base_cooldown_modifiable_repeat_interval_ms", - "value": 1600.0 + "value": 0.0 } ] } @@ -225689,11 +225689,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225718,11 +225718,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225747,11 +225747,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225776,11 +225776,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225805,11 +225805,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225834,11 +225834,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225863,11 +225863,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225892,11 +225892,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 37.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225921,11 +225921,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225950,11 +225950,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -225979,11 +225979,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226008,11 +226008,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 46.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 69.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226037,11 +226037,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 79.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226066,11 +226066,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 91.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226095,11 +226095,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 69.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 104.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226124,11 +226124,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 79.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 119.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226153,11 +226153,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 90.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 135.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226182,11 +226182,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 153.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226211,11 +226211,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 116.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226240,11 +226240,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 131.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226269,11 +226269,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 148.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 222.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226298,11 +226298,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 167.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 251.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226327,11 +226327,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 189.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 283.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226356,11 +226356,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 213.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 319.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226385,11 +226385,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 240.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 360.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226414,11 +226414,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 271.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 406.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226443,11 +226443,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 305.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 458.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226472,11 +226472,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 345.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 517.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226501,11 +226501,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 389.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 583.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226530,11 +226530,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 438.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 658.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226559,11 +226559,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 495.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 742.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226588,11 +226588,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 559.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 838.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226617,11 +226617,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 631.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 947.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226646,11 +226646,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 713.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1070.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226675,11 +226675,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 807.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1210.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226704,11 +226704,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 913.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1369.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226733,11 +226733,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1033.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1550.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226762,11 +226762,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1756.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226791,11 +226791,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1328.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1992.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -226820,11 +226820,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1507.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2260.0 + "value": 0.0 }, { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", @@ -229991,11 +229991,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230012,11 +230012,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230033,11 +230033,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230054,11 +230054,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230075,11 +230075,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230096,11 +230096,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230117,11 +230117,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230138,11 +230138,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230159,11 +230159,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230180,11 +230180,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230201,11 +230201,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230222,11 +230222,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230243,11 +230243,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230264,11 +230264,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230285,11 +230285,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 48.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 72.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230306,11 +230306,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230327,11 +230327,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230348,11 +230348,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 72.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230369,11 +230369,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 122.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230390,11 +230390,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 139.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230411,11 +230411,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 159.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230432,11 +230432,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 120.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 180.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230453,11 +230453,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 136.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 205.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230474,11 +230474,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 155.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 232.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230495,11 +230495,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 176.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 264.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230516,11 +230516,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 200.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 300.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230537,11 +230537,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 227.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 340.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230558,11 +230558,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 258.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 387.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230579,11 +230579,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 293.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 439.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230600,11 +230600,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 333.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 499.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230621,11 +230621,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 379.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 568.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230642,11 +230642,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 431.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 646.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230663,11 +230663,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 491.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 736.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230684,11 +230684,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 559.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 839.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230705,11 +230705,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 638.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 956.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230726,11 +230726,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 728.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1091.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230747,11 +230747,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 831.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1247.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230768,11 +230768,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 950.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1425.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230789,11 +230789,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1087.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1631.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -230810,11 +230810,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1245.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1868.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -232830,7 +232830,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 101.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -232859,7 +232859,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 136.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -232888,7 +232888,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 187.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -232917,7 +232917,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 244.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -232946,7 +232946,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 309.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -232975,7 +232975,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 383.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233004,7 +233004,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 466.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233033,7 +233033,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 561.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233062,7 +233062,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 669.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233091,7 +233091,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 791.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233120,7 +233120,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 930.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233149,7 +233149,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1087.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233178,7 +233178,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1266.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233207,7 +233207,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1470.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233236,7 +233236,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1702.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233265,7 +233265,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 1966.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233294,7 +233294,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2267.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233323,7 +233323,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 2611.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233352,7 +233352,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3004.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233381,7 +233381,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3453.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233410,7 +233410,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 3968.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233439,7 +233439,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 4557.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233468,7 +233468,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 5234.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233497,7 +233497,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 6011.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233526,7 +233526,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 6905.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233555,7 +233555,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 7934.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233584,7 +233584,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 9121.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233613,7 +233613,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 10491.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233642,7 +233642,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 12074.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233671,7 +233671,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 13905.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233700,7 +233700,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 16028.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233729,7 +233729,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 18490.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233758,7 +233758,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 21351.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233787,7 +233787,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 24680.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233816,7 +233816,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 28557.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233845,7 +233845,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 33080.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233874,7 +233874,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 38365.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233903,7 +233903,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 44547.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233932,7 +233932,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 51791.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -233961,7 +233961,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 60290.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -234152,7 +234152,7 @@ }, { "stat": "base_fire_damage_to_deal_per_minute", - "value": 104.0 + "value": 0.0 }, { "stat": "incinerate_fuel_gain_per_mana_spent", @@ -239221,11 +239221,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", @@ -242587,7 +242587,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242597,7 +242597,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242607,7 +242607,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242617,7 +242617,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242627,7 +242627,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242637,7 +242637,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242647,7 +242647,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242657,7 +242657,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242667,7 +242667,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 2.0 + "value": 0.0 } ] }, @@ -242677,7 +242677,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242687,7 +242687,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242697,7 +242697,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242707,7 +242707,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242717,7 +242717,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242727,7 +242727,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242737,7 +242737,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242747,7 +242747,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242757,7 +242757,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 3.0 + "value": 0.0 } ] }, @@ -242767,7 +242767,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242777,7 +242777,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242787,7 +242787,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242797,7 +242797,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242807,7 +242807,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242817,7 +242817,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242827,7 +242827,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242837,7 +242837,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242847,7 +242847,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 4.0 + "value": 0.0 } ] }, @@ -242857,7 +242857,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242867,7 +242867,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242877,7 +242877,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242887,7 +242887,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242897,7 +242897,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242907,7 +242907,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242917,7 +242917,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242927,7 +242927,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242937,7 +242937,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242947,7 +242947,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242957,7 +242957,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242967,7 +242967,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] }, @@ -242977,7 +242977,7 @@ "stats": [ { "stat": "lightning_arrow_maximum_number_of_extra_targets", - "value": 5.0 + "value": 0.0 } ] } @@ -255190,11 +255190,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 26.0 + "value": 0.0 } ] } @@ -290684,11 +290684,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -333421,7 +333421,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333442,7 +333442,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333463,7 +333463,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333484,7 +333484,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333505,7 +333505,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333526,7 +333526,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333547,7 +333547,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333568,7 +333568,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333589,7 +333589,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333610,7 +333610,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333631,7 +333631,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333652,7 +333652,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333673,7 +333673,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333694,7 +333694,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333715,7 +333715,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333736,7 +333736,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333757,7 +333757,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333778,7 +333778,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333799,7 +333799,7 @@ }, { "stat": "number_of_chains", - "value": 3.0 + "value": 0.0 } ] }, @@ -333820,7 +333820,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333841,7 +333841,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333862,7 +333862,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333883,7 +333883,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333904,7 +333904,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333925,7 +333925,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333946,7 +333946,7 @@ }, { "stat": "number_of_chains", - "value": 4.0 + "value": 0.0 } ] }, @@ -333967,7 +333967,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -333988,7 +333988,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334009,7 +334009,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334030,7 +334030,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334051,7 +334051,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334072,7 +334072,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334093,7 +334093,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334114,7 +334114,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334135,7 +334135,7 @@ }, { "stat": "number_of_chains", - "value": 5.0 + "value": 0.0 } ] }, @@ -334156,7 +334156,7 @@ }, { "stat": "number_of_chains", - "value": 6.0 + "value": 0.0 } ] }, @@ -334177,7 +334177,7 @@ }, { "stat": "number_of_chains", - "value": 6.0 + "value": 0.0 } ] }, @@ -334198,7 +334198,7 @@ }, { "stat": "number_of_chains", - "value": 6.0 + "value": 0.0 } ] }, @@ -334219,7 +334219,7 @@ }, { "stat": "number_of_chains", - "value": 6.0 + "value": 0.0 } ] }, @@ -334240,7 +334240,7 @@ }, { "stat": "number_of_chains", - "value": 7.0 + "value": 0.0 } ] } @@ -340196,11 +340196,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340230,11 +340230,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340264,11 +340264,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340298,11 +340298,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340332,11 +340332,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340366,11 +340366,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340400,11 +340400,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340434,11 +340434,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340468,11 +340468,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340502,11 +340502,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340536,11 +340536,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340570,11 +340570,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340604,11 +340604,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340638,11 +340638,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340672,11 +340672,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340706,11 +340706,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340740,11 +340740,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340774,11 +340774,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340808,11 +340808,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340842,11 +340842,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340876,11 +340876,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340910,11 +340910,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340944,11 +340944,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -340978,11 +340978,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341012,11 +341012,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341046,11 +341046,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341080,11 +341080,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341114,11 +341114,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341148,11 +341148,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341182,11 +341182,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341216,11 +341216,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341250,11 +341250,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341284,11 +341284,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341318,11 +341318,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341352,11 +341352,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341386,11 +341386,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341420,11 +341420,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341454,11 +341454,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341488,11 +341488,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -341522,11 +341522,11 @@ }, { "stat": "off_hand_minimum_added_physical_damage_per_15_shield_armour", - "value": 3.0 + "value": 0.0 }, { "stat": "off_hand_maximum_added_physical_damage_per_15_shield_armour", - "value": 5.0 + "value": 0.0 }, { "stat": "base_movement_velocity_+%", @@ -350779,19 +350779,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -350820,19 +350820,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -350861,19 +350861,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -350902,19 +350902,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 39.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 39.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -350943,19 +350943,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -350984,19 +350984,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 92.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 92.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351025,19 +351025,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 75.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351066,19 +351066,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 90.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 135.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 90.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 135.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351107,19 +351107,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 161.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 161.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351148,19 +351148,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 190.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351189,19 +351189,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351230,19 +351230,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 174.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 261.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 174.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 261.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351271,19 +351271,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 203.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 304.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 203.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 304.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351312,19 +351312,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 235.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 353.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 235.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 353.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351353,19 +351353,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 272.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 408.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 272.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 408.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351394,19 +351394,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 315.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 472.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 315.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 472.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351435,19 +351435,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 363.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 544.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 363.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 544.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351476,19 +351476,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 418.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 627.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 418.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 627.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351517,19 +351517,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 481.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 721.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 481.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 721.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351558,19 +351558,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 553.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 829.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 553.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 829.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351599,19 +351599,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 635.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 952.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 635.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 952.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351640,19 +351640,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 729.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1094.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 729.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1094.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351681,19 +351681,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 837.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1256.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 837.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1256.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351722,19 +351722,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 962.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1443.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 962.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1443.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351763,19 +351763,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1105.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1657.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 1105.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1657.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351804,19 +351804,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1270.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1904.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 1270.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1904.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351845,19 +351845,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1459.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2189.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 1459.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2189.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351886,19 +351886,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1679.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2518.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 1679.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2518.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351927,19 +351927,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1932.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2898.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 1932.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2898.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -351968,19 +351968,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2225.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3337.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 2225.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3337.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352009,19 +352009,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2564.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3847.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 2564.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3847.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352050,19 +352050,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2958.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 4438.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 2958.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 4438.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352091,19 +352091,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3416.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5124.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 3416.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 5124.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352132,19 +352132,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3949.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5923.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 3949.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 5923.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352173,19 +352173,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4569.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6854.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 4569.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 6854.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352214,19 +352214,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5293.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 7939.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 5293.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7939.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352255,19 +352255,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6138.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9208.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 6138.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9208.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352296,19 +352296,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7128.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 10691.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 7128.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 10691.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352337,19 +352337,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 8287.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 12430.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 8287.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 12430.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -352378,19 +352378,19 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 9646.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 14470.0 + "value": 0.0 }, { "stat": "spell_minimum_base_physical_damage", - "value": 9646.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 14470.0 + "value": 0.0 }, { "stat": "active_skill_requires_X_glory", @@ -353403,11 +353403,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353424,11 +353424,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353445,11 +353445,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353466,11 +353466,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353487,11 +353487,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353508,11 +353508,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 52.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 79.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353529,11 +353529,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 96.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353550,11 +353550,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 77.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353571,11 +353571,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 92.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353592,11 +353592,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 108.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 162.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353613,11 +353613,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 191.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353634,11 +353634,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353655,11 +353655,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 260.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353676,11 +353676,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 201.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 302.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353697,11 +353697,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 233.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 350.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353718,11 +353718,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 269.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 404.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353739,11 +353739,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 310.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 466.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353760,11 +353760,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 358.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 536.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353781,11 +353781,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 411.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 617.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353802,11 +353802,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 473.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 709.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353823,11 +353823,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 543.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 815.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353844,11 +353844,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 624.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 936.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353865,11 +353865,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 717.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1075.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353886,11 +353886,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 823.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1235.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353907,11 +353907,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 945.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1418.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353928,11 +353928,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1086.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1630.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353949,11 +353949,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1249.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1873.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353970,11 +353970,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1436.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2155.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -353991,11 +353991,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1653.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2480.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354012,11 +354012,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1904.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2856.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354033,11 +354033,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2195.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3292.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354054,11 +354054,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2532.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3798.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354075,11 +354075,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2923.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 4385.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354096,11 +354096,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3379.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5069.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354117,11 +354117,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3910.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5865.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354138,11 +354138,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4529.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6794.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354159,11 +354159,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5253.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 7880.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354180,11 +354180,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6100.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9149.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354201,11 +354201,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7091.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 10637.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354222,11 +354222,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 8255.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 12383.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -354287,11 +354287,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354308,11 +354308,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 19.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354329,11 +354329,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354350,11 +354350,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 50.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354371,11 +354371,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 42.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354392,11 +354392,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 52.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 79.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354413,11 +354413,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 64.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 96.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354434,11 +354434,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 77.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354455,11 +354455,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 92.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354476,11 +354476,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 108.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 162.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354497,11 +354497,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 191.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354518,11 +354518,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354539,11 +354539,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 260.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354560,11 +354560,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 201.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 302.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354581,11 +354581,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 233.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 350.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354602,11 +354602,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 269.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 404.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354623,11 +354623,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 310.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 466.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354644,11 +354644,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 358.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 536.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354665,11 +354665,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 411.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 617.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354686,11 +354686,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 473.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 709.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354707,11 +354707,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 543.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 815.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354728,11 +354728,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 624.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 936.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354749,11 +354749,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 717.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1075.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354770,11 +354770,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 823.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1235.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354791,11 +354791,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 945.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1418.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354812,11 +354812,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1086.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1630.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354833,11 +354833,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1249.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 1873.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354854,11 +354854,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1436.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2155.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354875,11 +354875,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1653.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2480.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354896,11 +354896,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 1904.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 2856.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354917,11 +354917,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2195.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3292.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354938,11 +354938,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2532.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 3798.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354959,11 +354959,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 2923.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 4385.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -354980,11 +354980,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3379.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5069.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -355001,11 +355001,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 3910.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 5865.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -355022,11 +355022,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 4529.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 6794.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -355043,11 +355043,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 5253.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 7880.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -355064,11 +355064,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 6100.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 9149.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -355085,11 +355085,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 7091.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 10637.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -355106,11 +355106,11 @@ "stats": [ { "stat": "spell_minimum_base_cold_damage", - "value": 8255.0 + "value": 0.0 }, { "stat": "spell_maximum_base_cold_damage", - "value": 12383.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -360030,11 +360030,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360047,11 +360047,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360064,11 +360064,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360081,11 +360081,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360098,11 +360098,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360115,11 +360115,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360132,11 +360132,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 33.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360149,11 +360149,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360166,11 +360166,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360183,11 +360183,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360200,11 +360200,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360217,11 +360217,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360234,11 +360234,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360251,11 +360251,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 58.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 109.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360268,11 +360268,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 67.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 125.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360285,11 +360285,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 77.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 143.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360302,11 +360302,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 163.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360319,11 +360319,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 100.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 186.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360336,11 +360336,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 212.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360353,11 +360353,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 130.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 242.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360370,11 +360370,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 148.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 275.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360387,11 +360387,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 168.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 312.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360404,11 +360404,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 191.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 355.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360421,11 +360421,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 217.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 403.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360438,11 +360438,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 246.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 457.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360455,11 +360455,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 280.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 520.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360472,11 +360472,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 318.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 590.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360489,11 +360489,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 361.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 670.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360506,11 +360506,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 410.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 762.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360523,11 +360523,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 466.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 866.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360540,11 +360540,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 530.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 985.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360557,11 +360557,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 603.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1120.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360574,11 +360574,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 687.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1276.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360591,11 +360591,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 783.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1454.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360608,11 +360608,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 893.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1658.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360625,11 +360625,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1019.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 1892.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360642,11 +360642,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1163.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 2161.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360659,11 +360659,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1330.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 2470.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360676,11 +360676,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1522.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 2826.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -360693,11 +360693,11 @@ "stats": [ { "stat": "spell_minimum_base_chaos_damage", - "value": 1743.0 + "value": 0.0 }, { "stat": "spell_maximum_base_chaos_damage", - "value": 3237.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -361513,15 +361513,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361550,15 +361550,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361587,15 +361587,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361624,15 +361624,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361661,15 +361661,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361698,15 +361698,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361735,15 +361735,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361772,15 +361772,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361809,15 +361809,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361846,15 +361846,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361883,15 +361883,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361920,15 +361920,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361957,15 +361957,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -361994,15 +361994,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362031,15 +362031,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 120.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362068,15 +362068,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 135.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362105,15 +362105,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 153.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362142,15 +362142,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 172.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362179,15 +362179,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 193.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362216,15 +362216,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 216.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362253,15 +362253,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 242.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362290,15 +362290,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 271.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362327,15 +362327,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 303.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362364,15 +362364,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362401,15 +362401,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 378.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362438,15 +362438,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 421.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362475,15 +362475,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 470.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362512,15 +362512,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 524.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362549,15 +362549,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 584.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362586,15 +362586,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 651.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362623,15 +362623,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 726.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362660,15 +362660,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 809.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362697,15 +362697,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 902.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362734,15 +362734,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1006.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362771,15 +362771,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1122.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362808,15 +362808,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1252.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362845,15 +362845,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1398.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362882,15 +362882,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1561.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362919,15 +362919,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 92.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1744.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -362956,15 +362956,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 103.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1949.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -397079,7 +397079,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397089,7 +397089,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397099,7 +397099,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397109,7 +397109,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397119,7 +397119,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397129,7 +397129,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397139,7 +397139,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 } ] }, @@ -397149,7 +397149,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397159,7 +397159,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397169,7 +397169,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397179,7 +397179,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397189,7 +397189,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397199,7 +397199,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397209,7 +397209,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397219,7 +397219,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 } ] }, @@ -397229,7 +397229,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397239,7 +397239,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397249,7 +397249,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397259,7 +397259,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397269,7 +397269,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397279,7 +397279,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397289,7 +397289,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397299,7 +397299,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 } ] }, @@ -397309,7 +397309,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397319,7 +397319,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397329,7 +397329,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397339,7 +397339,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397349,7 +397349,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397359,7 +397359,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397369,7 +397369,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397379,7 +397379,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397389,7 +397389,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397399,7 +397399,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397409,7 +397409,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397419,7 +397419,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397429,7 +397429,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397439,7 +397439,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397449,7 +397449,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397459,7 +397459,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] }, @@ -397469,7 +397469,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 } ] } @@ -397535,7 +397535,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397553,7 +397553,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397571,7 +397571,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397589,7 +397589,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397607,7 +397607,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397625,7 +397625,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397643,7 +397643,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397661,7 +397661,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397679,7 +397679,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397697,7 +397697,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397715,7 +397715,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397733,7 +397733,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397751,7 +397751,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397769,7 +397769,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397786,7 +397786,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397804,7 +397804,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397822,7 +397822,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397840,7 +397840,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397858,7 +397858,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397876,7 +397876,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397894,7 +397894,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397912,7 +397912,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397930,7 +397930,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397948,7 +397948,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397966,7 +397966,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -397984,7 +397984,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398002,7 +398002,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398020,7 +398020,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398038,7 +398038,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398056,7 +398056,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398074,7 +398074,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398092,7 +398092,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398110,7 +398110,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398128,7 +398128,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398146,7 +398146,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398164,7 +398164,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398182,7 +398182,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398200,7 +398200,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398218,7 +398218,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -398236,7 +398236,7 @@ "stats": [ { "stat": "base_number_of_tempest_bells_allowed", - "value": 4.0 + "value": 0.0 }, { "stat": "bell_ailment_threshold", @@ -420463,15 +420463,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420500,15 +420500,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420537,15 +420537,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420574,15 +420574,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420611,15 +420611,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420648,15 +420648,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420685,15 +420685,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420722,15 +420722,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420759,15 +420759,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420796,15 +420796,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420833,15 +420833,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420870,15 +420870,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420907,15 +420907,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420944,15 +420944,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -420981,15 +420981,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 80.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421018,15 +421018,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 90.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421055,15 +421055,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421092,15 +421092,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421129,15 +421129,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421166,15 +421166,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 144.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421203,15 +421203,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 162.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421240,15 +421240,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 181.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421277,15 +421277,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 202.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421314,15 +421314,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 226.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421351,15 +421351,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 252.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421388,15 +421388,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 281.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421425,15 +421425,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 313.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421462,15 +421462,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 349.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421499,15 +421499,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 389.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421536,15 +421536,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 434.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421573,15 +421573,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 484.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421610,15 +421610,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 539.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421647,15 +421647,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 601.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421684,15 +421684,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 671.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421721,15 +421721,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 39.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 748.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421758,15 +421758,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 835.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421795,15 +421795,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 932.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421832,15 +421832,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1041.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421869,15 +421869,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1163.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -421906,15 +421906,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 68.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1300.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422676,15 +422676,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422713,15 +422713,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422750,15 +422750,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422787,15 +422787,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422824,15 +422824,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422861,15 +422861,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422898,15 +422898,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422935,15 +422935,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -422972,15 +422972,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423009,15 +423009,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423046,15 +423046,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423083,15 +423083,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423120,15 +423120,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423157,15 +423157,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423194,15 +423194,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 80.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423231,15 +423231,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 90.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423268,15 +423268,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 102.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423305,15 +423305,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423342,15 +423342,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423379,15 +423379,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 144.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423416,15 +423416,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 162.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423453,15 +423453,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 181.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423490,15 +423490,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 202.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423527,15 +423527,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 226.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423564,15 +423564,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 252.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423601,15 +423601,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 281.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423638,15 +423638,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 313.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423675,15 +423675,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 349.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423712,15 +423712,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 389.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423749,15 +423749,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 434.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423786,15 +423786,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 484.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423823,15 +423823,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 539.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423860,15 +423860,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 601.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423897,15 +423897,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 671.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423934,15 +423934,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 39.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 748.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -423971,15 +423971,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 835.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -424008,15 +424008,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 932.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -424045,15 +424045,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1041.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -424082,15 +424082,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1163.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -424119,15 +424119,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 68.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1300.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -425807,11 +425807,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425828,11 +425828,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425849,11 +425849,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425870,11 +425870,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425891,11 +425891,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425912,11 +425912,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425933,11 +425933,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425954,11 +425954,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425975,11 +425975,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -425996,11 +425996,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426017,11 +426017,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426038,11 +426038,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426059,11 +426059,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426080,11 +426080,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 95.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426101,11 +426101,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 110.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426122,11 +426122,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426143,11 +426143,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 98.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 146.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426164,11 +426164,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426185,11 +426185,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426206,11 +426206,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426227,11 +426227,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 256.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426248,11 +426248,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 294.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426269,11 +426269,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 225.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426290,11 +426290,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 259.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 388.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426311,11 +426311,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 446.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426332,11 +426332,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 342.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 513.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426353,11 +426353,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 393.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 589.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426374,11 +426374,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 452.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 678.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426395,11 +426395,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 520.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 780.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426416,11 +426416,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 599.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 898.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426437,11 +426437,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 690.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1036.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426458,11 +426458,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 797.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1195.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426479,11 +426479,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 920.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1380.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426500,11 +426500,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1063.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1595.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426521,11 +426521,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1230.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1845.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426542,11 +426542,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1425.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2138.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426563,11 +426563,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1653.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2479.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426584,11 +426584,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1919.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2878.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426605,11 +426605,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2231.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3347.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426626,11 +426626,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2597.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3896.0 + "value": 0.0 }, { "stat": "spell_minimum_base_cold_damage", @@ -426659,11 +426659,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426680,11 +426680,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426701,11 +426701,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426722,11 +426722,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426743,11 +426743,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426764,11 +426764,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426785,11 +426785,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426806,11 +426806,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426827,11 +426827,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426848,11 +426848,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426869,11 +426869,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426890,11 +426890,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426911,11 +426911,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426932,11 +426932,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 95.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426953,11 +426953,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 110.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426974,11 +426974,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -426995,11 +426995,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 98.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 146.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427016,11 +427016,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427037,11 +427037,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427058,11 +427058,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427079,11 +427079,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 256.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427100,11 +427100,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 294.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427121,11 +427121,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 225.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427142,11 +427142,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 259.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 388.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427163,11 +427163,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 446.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427184,11 +427184,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 342.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 513.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427205,11 +427205,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 393.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 589.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427226,11 +427226,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 452.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 678.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427247,11 +427247,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 520.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 780.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427268,11 +427268,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 599.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 898.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427289,11 +427289,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 690.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1036.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427310,11 +427310,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 797.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1195.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427331,11 +427331,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 920.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1380.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427352,11 +427352,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1063.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1595.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427373,11 +427373,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1230.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1845.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427394,11 +427394,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1425.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2138.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427415,11 +427415,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1653.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2479.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427436,11 +427436,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1919.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2878.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427457,11 +427457,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2231.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3347.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427478,11 +427478,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2597.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3896.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -427511,11 +427511,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427532,11 +427532,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427553,11 +427553,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427574,11 +427574,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427595,11 +427595,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427616,11 +427616,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427637,11 +427637,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427658,11 +427658,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427679,11 +427679,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427700,11 +427700,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427721,11 +427721,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427742,11 +427742,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427763,11 +427763,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427784,11 +427784,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 95.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427805,11 +427805,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 110.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427826,11 +427826,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427847,11 +427847,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 98.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 146.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427868,11 +427868,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427889,11 +427889,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427910,11 +427910,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427931,11 +427931,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 256.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427952,11 +427952,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 294.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427973,11 +427973,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 225.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -427994,11 +427994,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 259.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 388.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428015,11 +428015,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 446.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428036,11 +428036,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 342.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 513.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428057,11 +428057,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 393.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 589.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428078,11 +428078,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 452.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 678.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428099,11 +428099,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 520.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 780.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428120,11 +428120,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 599.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 898.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428141,11 +428141,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 690.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1036.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428162,11 +428162,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 797.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1195.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428183,11 +428183,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 920.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1380.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428204,11 +428204,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1063.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1595.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428225,11 +428225,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1230.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1845.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428246,11 +428246,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1425.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2138.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428267,11 +428267,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1653.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2479.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428288,11 +428288,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1919.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2878.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428309,11 +428309,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2231.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3347.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428330,11 +428330,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2597.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3896.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -428363,11 +428363,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428384,11 +428384,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428405,11 +428405,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428426,11 +428426,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428447,11 +428447,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428468,11 +428468,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428489,11 +428489,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428510,11 +428510,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428531,11 +428531,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428552,11 +428552,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 51.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428573,11 +428573,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 40.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 60.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428594,11 +428594,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 70.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428615,11 +428615,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 55.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428636,11 +428636,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 95.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428657,11 +428657,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 110.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428678,11 +428678,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 127.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428699,11 +428699,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 98.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 146.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428720,11 +428720,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 112.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 169.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428741,11 +428741,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 129.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428762,11 +428762,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 149.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 223.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428783,11 +428783,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 256.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428804,11 +428804,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 196.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 294.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428825,11 +428825,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 225.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428846,11 +428846,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 259.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 388.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428867,11 +428867,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 297.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 446.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428888,11 +428888,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 342.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 513.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428909,11 +428909,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 393.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 589.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428930,11 +428930,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 452.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 678.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428951,11 +428951,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 520.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 780.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428972,11 +428972,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 599.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 898.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -428993,11 +428993,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 690.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1036.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429014,11 +429014,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 797.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1195.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429035,11 +429035,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 920.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1380.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429056,11 +429056,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1063.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1595.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429077,11 +429077,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1230.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 1845.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429098,11 +429098,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1425.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2138.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429119,11 +429119,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1653.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2479.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429140,11 +429140,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 1919.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 2878.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429161,11 +429161,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2231.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3347.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -429182,11 +429182,11 @@ "stats": [ { "stat": "spell_minimum_base_physical_damage", - "value": 2597.0 + "value": 0.0 }, { "stat": "spell_maximum_base_physical_damage", - "value": 3896.0 + "value": 0.0 }, { "stat": "spell_minimum_base_chaos_damage", @@ -443951,11 +443951,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -443976,11 +443976,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444001,11 +444001,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444026,11 +444026,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 23.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444051,11 +444051,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444076,11 +444076,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 24.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 36.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444101,11 +444101,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 29.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 44.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444126,11 +444126,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 52.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444151,11 +444151,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444176,11 +444176,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 73.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444201,11 +444201,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 57.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 85.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444226,11 +444226,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 99.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444251,11 +444251,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 76.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 114.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444276,11 +444276,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 88.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 132.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444301,11 +444301,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 101.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 151.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444326,11 +444326,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 115.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 173.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444351,11 +444351,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 132.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 198.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444376,11 +444376,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 150.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 226.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444401,11 +444401,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 171.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 257.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444426,11 +444426,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 195.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 293.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444451,11 +444451,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 222.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 333.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444476,11 +444476,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 252.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 378.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444501,11 +444501,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 287.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 430.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444526,11 +444526,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 325.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 488.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444551,11 +444551,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 370.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 554.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444576,11 +444576,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 420.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 629.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444601,11 +444601,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 477.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 715.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444626,11 +444626,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 541.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 812.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444651,11 +444651,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 615.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 923.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444676,11 +444676,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 699.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1049.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444701,11 +444701,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 795.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1193.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444726,11 +444726,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 905.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1357.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444751,11 +444751,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1030.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1546.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444776,11 +444776,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1174.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1761.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444801,11 +444801,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1339.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2008.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444826,11 +444826,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1528.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2292.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444851,11 +444851,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1745.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2618.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444876,11 +444876,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1995.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2993.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444901,11 +444901,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2283.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3424.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -444926,11 +444926,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 2615.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 3922.0 + "value": 0.0 }, { "stat": "blazing_cluster_maximum_number_of_projectiles_allowed", @@ -445720,15 +445720,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445757,15 +445757,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445794,15 +445794,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 17.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445831,15 +445831,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 21.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445868,15 +445868,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 1.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445905,15 +445905,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445942,15 +445942,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -445979,15 +445979,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 2.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446016,15 +446016,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446053,15 +446053,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 3.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446090,15 +446090,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446127,15 +446127,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446164,15 +446164,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446201,15 +446201,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446238,15 +446238,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 120.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446275,15 +446275,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 135.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446312,15 +446312,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 153.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446349,15 +446349,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 172.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446386,15 +446386,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 193.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446423,15 +446423,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 216.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446460,15 +446460,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 242.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 9.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446497,15 +446497,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 271.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446534,15 +446534,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 16.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 303.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446571,15 +446571,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 338.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446608,15 +446608,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 20.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 378.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446645,15 +446645,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 421.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446682,15 +446682,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 25.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 470.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446719,15 +446719,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 28.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 524.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446756,15 +446756,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 31.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 584.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446793,15 +446793,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 34.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 651.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446830,15 +446830,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 726.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446867,15 +446867,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 43.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 809.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446904,15 +446904,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 902.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446941,15 +446941,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1006.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -446978,15 +446978,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 59.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1122.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -447015,15 +447015,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 66.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1252.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -447052,15 +447052,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 74.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1398.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -447089,15 +447089,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1561.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -447126,15 +447126,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 92.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1744.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -447163,15 +447163,15 @@ "stats": [ { "stat": "spell_minimum_base_lightning_damage", - "value": 103.0 + "value": 0.0 }, { "stat": "spell_maximum_base_lightning_damage", - "value": 1949.0 + "value": 0.0 }, { "stat": "base_number_of_projectiles", - "value": 13.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451455,7 +451455,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 17.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451476,7 +451476,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451497,7 +451497,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 37.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451518,7 +451518,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 49.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451539,7 +451539,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 63.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451560,7 +451560,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 79.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451581,7 +451581,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 97.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451602,7 +451602,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 117.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451623,7 +451623,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 140.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451644,7 +451644,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 166.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451665,7 +451665,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 194.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451686,7 +451686,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 226.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451707,7 +451707,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 262.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451728,7 +451728,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 301.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451749,7 +451749,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 344.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451770,7 +451770,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 392.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451791,7 +451791,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 443.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451812,7 +451812,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 500.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451833,7 +451833,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 561.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451854,7 +451854,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 628.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451875,7 +451875,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 701.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451896,7 +451896,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 777.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451917,7 +451917,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 857.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451938,7 +451938,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 940.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451959,7 +451959,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1027.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -451980,7 +451980,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1118.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452001,7 +452001,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1212.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452022,7 +452022,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1310.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452043,7 +452043,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1411.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452064,7 +452064,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1516.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452085,7 +452085,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1625.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452106,7 +452106,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1737.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452127,7 +452127,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1853.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452148,7 +452148,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 1972.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452169,7 +452169,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 2095.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452190,7 +452190,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 2222.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452211,7 +452211,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 2351.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452232,7 +452232,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 2485.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452253,7 +452253,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 2622.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -452274,7 +452274,7 @@ "stats": [ { "stat": "cast_on_damage_taken_threshold", - "value": 2763.0 + "value": 0.0 }, { "stat": "spell_minimum_base_lightning_damage", @@ -453970,7 +453970,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -453988,7 +453988,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454006,7 +454006,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454024,7 +454024,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454041,7 +454041,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454059,7 +454059,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454077,7 +454077,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454095,7 +454095,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454113,7 +454113,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454131,7 +454131,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454149,7 +454149,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454167,7 +454167,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454185,7 +454185,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454203,7 +454203,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454221,7 +454221,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454239,7 +454239,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454257,7 +454257,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454275,7 +454275,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 2.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454293,7 +454293,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454311,7 +454311,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454329,7 +454329,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454347,7 +454347,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454365,7 +454365,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454383,7 +454383,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454401,7 +454401,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454419,7 +454419,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454437,7 +454437,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454455,7 +454455,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454473,7 +454473,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454491,7 +454491,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454509,7 +454509,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454527,7 +454527,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454545,7 +454545,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454563,7 +454563,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454581,7 +454581,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454599,7 +454599,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454617,7 +454617,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454635,7 +454635,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454653,7 +454653,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454671,7 +454671,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 3.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -454800,7 +454800,7 @@ "stats": [ { "stat": "number_of_vine_arrow_pod_allowed", - "value": 1.0 + "value": 0.0 }, { "stat": "base_chaos_damage_to_deal_per_minute", @@ -457870,11 +457870,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -457895,11 +457895,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -457920,11 +457920,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -457945,11 +457945,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -457970,11 +457970,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -457995,11 +457995,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458020,11 +458020,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458045,11 +458045,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458070,11 +458070,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458095,11 +458095,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458120,11 +458120,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458145,11 +458145,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458170,11 +458170,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458195,11 +458195,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458220,11 +458220,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458245,11 +458245,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458270,11 +458270,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 122.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458295,11 +458295,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 140.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458320,11 +458320,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 159.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458345,11 +458345,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 121.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 181.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458370,11 +458370,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 206.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458395,11 +458395,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 156.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 234.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458420,11 +458420,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 177.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 266.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458445,11 +458445,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 201.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 302.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458470,11 +458470,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 229.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 343.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458495,11 +458495,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 260.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 390.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458520,11 +458520,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 295.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 443.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458545,11 +458545,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 335.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 503.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458570,11 +458570,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 381.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 571.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458595,11 +458595,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 433.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 649.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458620,11 +458620,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 492.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 738.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458645,11 +458645,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 560.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 840.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458670,11 +458670,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 638.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 957.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458695,11 +458695,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 727.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1090.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458720,11 +458720,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 829.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1243.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458745,11 +458745,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 946.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1419.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458770,11 +458770,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1080.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1620.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458795,11 +458795,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1235.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1853.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458820,11 +458820,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1413.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2120.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458845,11 +458845,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1619.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2428.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458958,11 +458958,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 4.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 6.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -458983,11 +458983,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 5.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 8.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459008,11 +459008,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 7.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 11.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459033,11 +459033,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 10.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 14.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459058,11 +459058,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 12.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459083,11 +459083,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 15.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459108,11 +459108,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 18.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 27.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459133,11 +459133,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 22.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 32.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459158,11 +459158,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 26.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 38.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459183,11 +459183,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 30.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 45.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459208,11 +459208,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 35.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 53.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459233,11 +459233,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 41.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 61.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459258,11 +459258,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 47.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459283,11 +459283,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 54.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 81.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459308,11 +459308,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 62.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 94.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459333,11 +459333,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 71.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 107.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459358,11 +459358,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 82.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 122.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459383,11 +459383,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 93.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 140.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459408,11 +459408,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 106.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 159.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459433,11 +459433,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 121.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 181.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459458,11 +459458,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 137.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 206.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459483,11 +459483,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 156.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 234.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459508,11 +459508,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 177.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 266.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459533,11 +459533,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 201.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 302.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459558,11 +459558,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 229.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 343.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459583,11 +459583,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 260.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 390.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459608,11 +459608,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 295.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 443.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459633,11 +459633,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 335.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 503.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459658,11 +459658,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 381.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 571.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459683,11 +459683,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 433.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 649.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459708,11 +459708,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 492.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 738.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459733,11 +459733,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 560.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 840.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459758,11 +459758,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 638.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 957.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459783,11 +459783,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 727.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1090.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459808,11 +459808,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 829.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1243.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459833,11 +459833,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 946.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1419.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459858,11 +459858,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1080.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1620.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459883,11 +459883,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1235.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 1853.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459908,11 +459908,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1413.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2120.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", @@ -459933,11 +459933,11 @@ "stats": [ { "stat": "spell_minimum_base_fire_damage", - "value": 1619.0 + "value": 0.0 }, { "stat": "spell_maximum_base_fire_damage", - "value": 2428.0 + "value": 0.0 }, { "stat": "spell_minimum_base_fire_damage", diff --git a/pipeline/config.json b/pipeline/config.json index a106617b..36d28cc4 100644 --- a/pipeline/config.json +++ b/pipeline/config.json @@ -11,7 +11,7 @@ { "name": "SkillGems", "columns": ["BaseItemType","GemType","GemColour","MinLevelReq","StrengthRequirementPercent","DexterityRequirementPercent","IntelligenceRequirementPercent","GemEffects","ItemExperienceType"] }, { "name": "GrantedEffects", "columns": ["Id","IsSupport","ActiveSkill","StatSet","CastTime","CostTypes","AllowedActiveSkillTypes","AddedActiveSkillTypes","ExcludedActiveSkillTypes","SupportsGemsOnly","CannotBeSupported","AdditionalStatSets"] }, { "name": "GrantedEffectsPerLevel", "columns": ["GrantedEffect","Level","Cooldown","CostAmounts","AttackTime","CostMultiplier","StoredUses","Reservation","EffectOnPlayer","AttackSpeedMultiplier"] }, - { "name": "GrantedEffectStatSets", "columns": ["Id","BaseEffectiveness","IncrementalEffectiveness","DamageIncrementalEffectiveness","ImplicitStats","ConstantStats","ConstantStatsValues","Label"] }, + { "name": "GrantedEffectStatSets", "columns": ["Id","BaseEffectiveness","IncrementalEffectiveness","DamageIncrementalEffectiveness","ImplicitStats","ConstantStats","ConstantStatsValues","Label","IgnoredStats"] }, { "name": "GrantedEffectStatSetsPerLevel", "columns": ["StatSet","GemLevel","BaseMultiplier","BaseResolvedValues","FloatStats","FloatStatsValues","AdditionalStats","AdditionalStatsValues","SpellCritChance","AttackCritChance"] }, { "name": "ActiveSkills", "columns": ["Id","DisplayedName","Description","ActiveSkillTypes","GrantedEffect"] }, { "name": "ActiveSkillType", "columns": ["Id"] }, diff --git a/tools/pob2-oracle/oracle.lua b/tools/pob2-oracle/oracle.lua index d8d72c0f..61b5375b 100644 --- a/tools/pob2-oracle/oracle.lua +++ b/tools/pob2-oracle/oracle.lua @@ -771,6 +771,19 @@ if mainSkill then if vv then skillInfo.skillFlags[k] = true end end end + -- Selected statSet (multi-set gems like Essence Drain: [1]=Projectile, + -- [2]=Damage over Time) — which set drives the main output. + if ae and ae.statSet then + skillInfo.statSetIndex = ae.statSet.index + if ae.statSet.statSet then + skillInfo.statSetLabel = ae.statSet.statSet.label + end + end + if mainSkill.skillData then + skillInfo.chaosMin = mainSkill.skillData.ChaosMin + skillInfo.chaosMax = mainSkill.skillData.ChaosMax + skillInfo.chaosDot = mainSkill.skillData.ChaosDot + end -- skillData.dpsMultiplier (post calcLib.mod "DPS" fold, CalcOffence.lua:3863) for -- the W-C4 dps end-factor parity checks. if mainSkill.skillData then diff --git a/tools/pobr-data-adapter/src/required_columns.rs b/tools/pobr-data-adapter/src/required_columns.rs index 1d3870bc..886a9435 100644 --- a/tools/pobr-data-adapter/src/required_columns.rs +++ b/tools/pobr-data-adapter/src/required_columns.rs @@ -141,6 +141,9 @@ const REQUIRED_EN: &[(&str, &[&str])] = &[ "BaseEffectiveness", "ConstantStats", "ConstantStatsValues", + // (存量 #7-2)社区 schema 名 IgnoredStats = vendor RemoveStats: + // DoT 变体 set 移除主 set 击中伤害 stat(缺列 → 不移除,幻影击中回归)。 + "IgnoredStats", ], ), ( diff --git a/tools/pobr-data-adapter/src/skills/stat_sets.rs b/tools/pobr-data-adapter/src/skills/stat_sets.rs index fbb2f5fc..fe0ca805 100644 --- a/tools/pobr-data-adapter/src/skills/stat_sets.rs +++ b/tools/pobr-data-adapter/src/skills/stat_sets.rs @@ -41,6 +41,14 @@ struct RawStatSet { constant_stats: Vec, #[serde(rename = "ConstantStatsValues", default)] constant_stats_values: Vec, + /// (存量 #7-2)本 set 要**移除**的 stat(`Stats` 行索引)——社区 schema 列名 + /// `IgnoredStats` = vendor `Export/spec.lua` 的 `RemoveStats`。vendor 导出 + /// (`Export/Scripts/skills.lua:572-597`)把合并行里这些 stat 的值置 0(只置 + /// **首次出现**,即 base-merge 拼入的主 set 副本)。典型 = Essence Drain 的 + /// DoT set 移除主 set 的 `spell_min/max_base_chaos_damage`(纯 DoT 无击中)。 + /// 列缺失(旧表下载)→ 空 = 不移除(韧性降级,与既有缺列口径一致)。 + #[serde(rename = "IgnoredStats", default)] + ignored_stats: Vec, } #[derive(Deserialize)] @@ -250,6 +258,21 @@ pub fn adapt_stat_sets(en: &Path) -> Result { let raw_multiplier = |row: &RawStatSetPerLevel| { 1.0 + f64::from(row.base_multiplier.unwrap_or(0) as i32) / 10000.0 }; + // (存量 #7-2)RemoveStats/IgnoredStats 置零(vendor skills.lua:589-596): + // 对 set 声明移除的每个 stat,把行内**首次出现**的值置 0(合并行中先出现的 + // 是 base-merge 拼入的主 set 副本),其余出现保留。 + // ponytail: 同一 stat 在 IgnoredStats 重复出现时 vendor 会置零多个占位—— + // 当前数据无此形态,重复项按单次处理。 + let apply_ignored = |stats: &mut Vec, ignored: &[usize]| { + for &idx in ignored { + let Some(sid) = stat_id.get(idx).filter(|s| !s.is_empty()) else { + continue; + }; + if let Some(slot) = stats.iter_mut().find(|s| &s.stat == sid) { + slot.value = 0.0; + } + } + }; let mut out = Vec::new(); let mut damage_levels_total = 0usize; @@ -273,7 +296,8 @@ pub fn adapt_stat_sets(en: &Path) -> Result { let Some(gem_level) = row.gem_level.filter(|&l| l > 0).map(|l| l as u32) else { continue; }; - let stats = resolve_row_stats(row); + let mut stats = resolve_row_stats(row); + apply_ignored(&mut stats, &main_set.ignored_stats); let damage_multiplier = raw_multiplier(row); // 收录有 stat 或有非平凡倍率的等级行。 if !stats.is_empty() || (damage_multiplier - 1.0).abs() > f64::EPSILON { @@ -333,6 +357,9 @@ pub fn adapt_stat_sets(en: &Path) -> Result { let paired = main_rows.get(indx); let mut stats = paired.map(|p| resolve_row_stats(p)).unwrap_or_default(); stats.extend(resolve_row_stats(row)); + // 本 set 的 RemoveStats 作用于**合并后**的行(vendor 先 tableConcat + // 再置零):Essence Drain DoT set 置零主 set 拼入的 62/115 击中段。 + apply_ignored(&mut stats, &add_set.ignored_stats); // 倍率:本行 BaseMultiplier ≠ 0 取本行(:533-541 两分支终值同为本行 // `/10000+1`;UseSetAttackMulti 列未下载),否则回退主配对行。 let damage_multiplier = if row.base_multiplier.unwrap_or(0) != 0 { From 6bb068547f9c318cd63ac3e66d24b68eff8ae13d Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 16:15:06 +0800 Subject: [PATCH 37/74] =?UTF-8?q?fix(calc,data):=20ritualist=20EHP=201.10x?= =?UTF-8?q?=20+=20wolf-pack=20Life/Armour=20=E2=80=94=20retire=20phantom?= =?UTF-8?q?=20charm=20Guard,=20halved=20Str=20life,=20Spirit=20denominator?= =?UTF-8?q?,=20pool-name=20normalization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backlog items #7-3 and #7-4 (pre-0.5.4b gaps). #7-3 ritualist TotalEHP 1.10x: the suspected LowLife LifeRecoverable cap was a red herring — vendor caps only under an explicit conditionLowLife config input (CalcDefence.lua:2670-2676) and both sides already agree (LifeRecoverable = LifeUnreserved = 23). The real cause: the curated special mod 'also_grants_guard' (charm 'Also grants N Guard' -> 491 shared GuardAbsorb) has no vendor counterpart — PoB2's ModParser does not parse the line at all (oracle AnyGuard=False; the entry itself was marked verified:false / 'more modeling than PoB2'). Against the PoB2 golden this is a phantom +491 to every max-hit pool. Removed from the overlay (re-add when vendor models it); precompiled parsed_mods.json regenerated (the lines now fall to visible Unsupported). Result: ritualist TotalEHP/5xMaxHit all exact (1.19x chaos -> 1.00x); monk-twister maxhits 1.06x -> 0.97x, EHP 1.05x -> 0.98x. #7-4 wolf-pack (oracle enemyMitigation + extraModList pinned each): - Life 1.11x: Giant's Blood keystone 'Inherent Life granted by Strength is halved' parsed to the HalvesLifeFromStrength flag but had no consumer — attribute derivation now mirrors vendor CalcPerform.lua:500-505 (Str x1 instead of x2, class-baked part corrected). 2973.6 -> 2674.1 vs golden 2674 (exact). - Armour 0.98x: PerStat Spirit denominator was BASE-only (300); vendor PerStat reads output.Spirit (336). New CalculationSession::spirit_total (calc_spirit_pool) feeds the multiplier/stat backfill. Perfidy '+2 Armour per 1 Spirit' now 672; Armour 18169.78 -> 18579.90 vs golden 18580 (exact). - Mana 0.94x -> 0.99x and gemling Life/Mana/5xMaxHit/EHP all -> 1.00x: the overlay-common 'threshold_10_{red,blue}_support_gems_5pct' entries emitted vendor names Life/Mana, which are dead buckets in PoBR (pool aggregation reads MaximumLife/MaximumMana per the parser name_map) — normalized. - ChaosMaxHit/EHP remainder decomposed, NOT closed (companion project): vendor extends every hit pool by ally absorption — the wolf companion takes CompanionAllyDamageMitigation=10% of damage capped by TotalCompanionLife=2262, i.e. pool/(1-0.10)=3826.67 vs raw 3444 (oracle pinned; CalcDefence.lua:3567-3595 allies layer). PoBR has no companion actor yet, so maxhits sit at ~0.82x honestly (previously masked to ~0.96x by the phantom guard). Also: POBR_DBG_EHPPOOL pool-decomposition dump (ehp.rs) and POBR_DUMP_DEFENCE on the dump_components example (both used for the oracle diffs above). Baselines (honest): def 25-col @5% 413->425 (@10% 434 unchanged), def core-8 @5% 140->142; off/dot/panel unchanged. --- crates/pobr-build/examples/dump_components.rs | 24 ++++++++ .../pobr-build/src/calc_orchestrator/mod.rs | 21 ++++++- .../pobr-build/tests/parity/ninja_parity.rs | 4 +- crates/pobr-core/src/calc/ehp.rs | 8 +++ crates/pobr-core/src/calc/session.rs | 11 ++++ data/4.5.4.3/generated/parsed_mods.json | 60 ++----------------- data/4.5.4.3/overlay/special_mods.json | 20 ------- data/overlay-common/special_mods.json | 8 +-- 8 files changed, 74 insertions(+), 82 deletions(-) diff --git a/crates/pobr-build/examples/dump_components.rs b/crates/pobr-build/examples/dump_components.rs index d38deb2f..90dd4852 100644 --- a/crates/pobr-build/examples/dump_components.rs +++ b/crates/pobr-build/examples/dump_components.rs @@ -42,6 +42,30 @@ fn main() { out.action_rate, out.hit_chance ); + // POBR_DUMP_DEFENCE=1:附加防御/EHP 池分解(与 oracle mainOutput 的 + // TotalHitPool / LifeRecoverable / MaximumHitTaken 族对照)。 + if std::env::var("POBR_DUMP_DEFENCE").is_ok() { + println!( + " life={:.2} life_unres={:.2} life_recoverable={:.2} es={:.2} es_cap={:.2} ward={:.2} mana={:.2} mana_unres={:.2}", + out.life, + out.life_unreserved, + out.life_recoverable, + out.energy_shield, + out.energy_shield_recovery_cap, + out.ward, + out.mana, + out.mana_unreserved, + ); + println!( + " maxhit: phys={:.1} fire={:.1} cold={:.1} light={:.1} chaos={:.1} total_ehp={:.1}", + out.physical_max_hit_pob2, + out.fire_max_hit_pob2, + out.cold_max_hit_pob2, + out.lightning_max_hit_pob2, + out.chaos_max_hit_pob2, + out.total_ehp_pob2, + ); + } let mut sum = 0.0; for c in &out.damage_components { let avg = c.avg(); diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index e92fcbd2..0bb59fa2 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -1429,6 +1429,17 @@ fn inject_attribute_derivation( let str_total = session.attribute_total("Strength", cls_str); let dex_total = session.attribute_total("Dexterity", cls_dex); let int_total = session.attribute_total("Intelligence", cls_int); + // (存量 #7-4)Giant's Blood 键石「Inherent Life granted by Strength is + // halved」(vendor CalcPerform.lua:500-505:flag HalvesLifeFromStrength → + // `Life BASE = Str × 1` 而非 ×2)。CharacterBase 已烘焙职业起始段 + // `cls_str × life_per_strength`,此处增量按「目标总量 − 烘焙段」注入, + // 使 Str 派生生命总量 = str_total × 减半后系数(wolf-pack 802→401, + // oracle Life 逐源钉值)。 + let life_per_str = if session.has_flag("HalvesLifeFromStrength") { + cc.life_per_strength / 2.0 + } else { + cc.life_per_strength + }; let mk = |stat: &str, value: f64| { let origin = ModifierSource::new(SourceId::new( SourceKind::CharacterBase, @@ -1438,7 +1449,10 @@ fn inject_attribute_derivation( Modifier::number(stat, ModType::Base, value).with_origin(origin) }; session.add_modifiers([ - mk("MaximumLife", cc.life_per_strength * (str_total - cls_str)), + mk( + "MaximumLife", + str_total * life_per_str - cls_str * cc.life_per_strength, + ), mk( "MaximumMana", cc.mana_per_intelligence * (int_total - cls_int), @@ -1495,7 +1509,10 @@ fn inject_per_x_multipliers(session: &mut CalculationSession, build: &Build, dat let str_total = session.base_sum("Strength"); let dex_total = session.base_sum("Dexterity"); let int_total = session.base_sum("Intelligence"); - let spirit_total = session.base_sum("Spirit"); + // (存量 #7-4)Spirit 分母 = **最终池值**(calc_spirit_pool,含 INC/MORE 与 + // 转换扣减)——vendor PerStat 读 output.Spirit;BASE-only 会把 wolf-pack + // Perfidy「+2 Armour per 1 Spirit」欠算 72 base(Spirit 336 vs base 300)。 + let spirit_total = session.spirit_total(); let mana_total = session.pool_total("MaximumMana"); let life_total = session.pool_total("MaximumLife"); session.set_multiplier("Strength", str_total); diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index fd413b2a..2aa318c0 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -533,7 +533,7 @@ fn compute_tallies(verbose: bool) -> (Tally, Tally, Tally, Tally, Vec) { // Atziri's Communion 的 Spirit→Life 保留转换(LifeReservePercentPerSpirit, // vendor CalcDefence.lua:248-254)接入后双列翻正;abyssal-lich(同戴 Communion) // SpiritUnres inf→1.00x 同根。见 buffs.rs spirit_reservation_modifiers 转换分支。 -const BASELINE_DEF_CORE_HIT5: usize = 140; // Communion 后 140/144(ItemES 138;Barrier-Life 136;Mageblood 135;迁移基线 118;0.5.0=139) +const BASELINE_DEF_CORE_HIT5: usize = 142; // 存量 #7-3/4(charm-guard 摘除 + wolf-pack Life/Armour 收敛 + gemling 池名归一)后 142/144(Communion 140(ItemES 138;Barrier-Life 136;Mageblood 135;迁移基线 118;0.5.0=139) // **per-socket-filled 修复重记(+1 @5%/@10%)**:gemling-legionnaire 身甲 Morior Invictus // `+14 to Spirit per Socket filled`(×5 socket)经 `RunesSocketedIn{SlotName}` Multiplier // 接入 → Spirit 180→250(0.72x→1.00x,翻正)。详见 collect.rs::filter_parseable 闸门 + @@ -638,7 +638,7 @@ const BASELINE_DEF_CORE_HIT5: usize = 140; // Communion 后 140/144(ItemES 138 // **0.5.4b #4 Communion/LowLife + Voices 重记(+3 @5% 410→413 / +5 @10% 429→434)**: // core-8 的 SpiritUnres/LifeUnres 翻正(见 BASELINE_DEF_CORE_HIT5 上说明)+ // abyssal-lich EnergyShield 0.93x→0.98x(Voices sinister 珠宝的 ES 词条找回)。 -const BASELINE_DEF_HIT5: usize = 413; // Communion+Voices 后 413/450(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +const BASELINE_DEF_HIT5: usize = 425; // 存量 #7-3/4 后 425/450(ritualist 6 格 + gemling 9 格 + wolf-pack Life/Armour 精确翻正,wolf-pack MaxHit 族随 charm-guard 掩蔽摘除移出——余量 = companion ally-mitigation 层;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 diff --git a/crates/pobr-core/src/calc/ehp.rs b/crates/pobr-core/src/calc/ehp.rs index 763150cb..e5dc6f5e 100644 --- a/crates/pobr-core/src/calc/ehp.rs +++ b/crates/pobr-core/src/calc/ehp.rs @@ -1209,6 +1209,14 @@ pub fn fill_ehp_pob2( // ---- 新口径 max hit(:3540-3697)---- let pool_by_type = total_hit_pools(&mom, es_recovery_cap, &pools, &ctx); + // 诊断:POBR_DBG_EHPPOOL=1 时 dump 池分解(与 oracle TotalHitPool / + // MoMHitPool / Ward 对照)。 + if std::env::var("POBR_DBG_EHPPOOL").is_ok() { + eprintln!( + "[POBR_EHPPOOL] pools={pool_by_type:?} mom={mom:?} es_cap={es_recovery_cap:.2} ward={:.2} guard=({:.2},{:.2}) aegis_shared={:.2}", + pools.ward, pools.guard_shared, pools.guard_shared_rate, pools.aegis_shared, + ); + } let inputs = MaxHitInputs { mit: &mit, pools_full: &pools, diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index c36683ec..c19d7226 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -511,6 +511,17 @@ impl CalculationSession { /// (CalcOffence pool 段)。供编排层在全部来源注入后回填 PerStat 资源分母 /// (vendor PerStat tag 读 actor **output**,ModStore.lua:440-460 GetStat)—— /// [`base_sum`](Self::base_sum) 只取 BASE 之和,会漏掉 inc/more 缩放后的池值。 + /// Spirit 最终池值(vendor `output.Spirit`,[`calc_spirit_pool`] 同源: + /// OVERRIDE → (base + Extra) × 未转换比例 × (1+Σinc/100) × Πmore,round)。 + /// 供编排层回填 PerStat `Spirit` 分母——vendor PerStat 读 actor output + /// (ModStore.lua:440-460 GetStat),BASE-only 会把「+2 Armour per 1 Spirit」 + /// (wolf-pack Perfidy,Spirit 336 vs base 300)欠算。 + /// + /// [`calc_spirit_pool`]: super::calc_spirit_pool + pub fn spirit_total(&self) -> f64 { + super::calc_spirit_pool(&self.env.player.mod_db, &self.env.cfg) + } + pub fn pool_total(&self, name: &str) -> f64 { let actor_base = match name { "MaximumLife" => self.env.player.base.life, diff --git a/data/4.5.4.3/generated/parsed_mods.json b/data/4.5.4.3/generated/parsed_mods.json index af6d5cab..39adee15 100644 --- a/data/4.5.4.3/generated/parsed_mods.json +++ b/data/4.5.4.3/generated/parsed_mods.json @@ -25335,7 +25335,7 @@ "status": "parsed", "mods": [ { - "name": "Life", + "name": "MaximumLife", "type": "Inc", "value": 5.0, "tags": [ @@ -25349,7 +25349,7 @@ "status": "parsed", "mods": [ { - "name": "Mana", + "name": "MaximumMana", "type": "Inc", "value": 5.0, "tags": [ @@ -33015,67 +33015,19 @@ }, { "text": "Also grants 180 Guard", - "status": "parsed", - "mods": [ - { - "name": "GuardAbsorbRate", - "type": "Base", - "value": 100.0 - }, - { - "name": "GuardAbsorbLimit", - "type": "Base", - "value": 180.0 - } - ] + "status": "unsupported" }, { "text": "Also grants 265 Guard", - "status": "parsed", - "mods": [ - { - "name": "GuardAbsorbRate", - "type": "Base", - "value": 100.0 - }, - { - "name": "GuardAbsorbLimit", - "type": "Base", - "value": 265.0 - } - ] + "status": "unsupported" }, { "text": "Also grants 435 Guard", - "status": "parsed", - "mods": [ - { - "name": "GuardAbsorbRate", - "type": "Base", - "value": 100.0 - }, - { - "name": "GuardAbsorbLimit", - "type": "Base", - "value": 435.0 - } - ] + "status": "unsupported" }, { "text": "Also grants 491 Guard", - "status": "parsed", - "mods": [ - { - "name": "GuardAbsorbRate", - "type": "Base", - "value": 100.0 - }, - { - "name": "GuardAbsorbLimit", - "type": "Base", - "value": 491.0 - } - ] + "status": "unsupported" }, { "text": "Amber Amulet", diff --git a/data/4.5.4.3/overlay/special_mods.json b/data/4.5.4.3/overlay/special_mods.json index f4d718cc..69cc931d 100644 --- a/data/4.5.4.3/overlay/special_mods.json +++ b/data/4.5.4.3/overlay/special_mods.json @@ -98,26 +98,6 @@ "batch": "S0", "source_note": "金护符阿兹莫里之灵附身(Stag/Wolf/Bear…):玩家侧 buff 数值无可验证一手来源(poe2db/wiki 仅载敌方附身效果,BuffDefinitions 未入库)→ 暂解析为 FLAG 登记,数据可得后升级 handler 接具体 buff" }, - { - "id": "also_grants_guard", - "pattern": "also grants (\\d+) guard", - "vendor_pattern": null, - "mods": [ - { - "name": "GuardAbsorbRate", - "type": "BASE", - "value": 100 - }, - { - "name": "GuardAbsorbLimit", - "type": "BASE", - "value": "$1" - } - ], - "verified": false, - "batch": "S0", - "source_note": "药剂/护符使用效果:PoE2 Guard buff=生命/ES 前全额吸收击中的缓冲池(agent-docs/active-defences.md §2;poe2db Guard 定义)→ rate 100 + limit $1,消费端 pool_setup.rs:132 现成(EHP/MaxHit 承受层)。vendor ModParser 未接此词条(比 PoB2 多建模一层)" - }, { "id": "armour_applies_to_fcl_instead_of_physical", "pattern": "armour applies to fire, cold and lightning damage taken from hits instead of physical damage", diff --git a/data/overlay-common/special_mods.json b/data/overlay-common/special_mods.json index 13ae0afe..8fa7eef6 100644 --- a/data/overlay-common/special_mods.json +++ b/data/overlay-common/special_mods.json @@ -1722,7 +1722,7 @@ "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", "mods": [ { - "name": "Life", + "name": "MaximumLife", "type": "INC", "value": 5, "tags": [ @@ -1736,7 +1736,7 @@ ], "verified": false, "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" + "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值;名归一 MaximumLife(parser name_map 口径,裸 Life 是死桶——wolf-pack 5% INC 漏计根因)" }, { "id": "threshold_10_blue_support_gems_5pct", @@ -1744,7 +1744,7 @@ "vendor_pattern": "if you have at least (%d+) (%a+) support gems socketed", "mods": [ { - "name": "Mana", + "name": "MaximumMana", "type": "INC", "value": 5, "tags": [ @@ -1758,7 +1758,7 @@ ], "verified": false, "batch": "a2-real-gaps", - "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值" + "source_note": "A2 真缺口 #4/#5/#6(oracle: INC 5 + MultiplierThreshold{SupportGems,10},vendor ModParser.lua:1697 函数 tag);下界阈值盲产欠算安全(cfg.multiplier 未灌 SupportGems 时不生效,编排层计数接线后自动生效);DSL 不吃捕获——按语料钉值;名归一 MaximumMana(同 red 条目)" }, { "id": "threshold_10_green_support_gems_5pct", From a208a17209e55720550e2d80c49de80ca97c59b1 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 16:17:12 +0800 Subject: [PATCH 38/74] docs: record backlog sweep #7 outcomes in adapting-to-0.5.4b Remaining --- docs/adapting-to-0.5.4b.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 807e6b7f..4df88b1c 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -178,6 +178,10 @@ documented in pob2_parity.rs), and frost-bomb 0.66x (golden unchanged 0.5.0→0.5.4b — a pre-existing cooldown-DPS gap, not a 0.5.4b regression). These moved 6-46% in the 0.5.4b golden with per-build factors — no shared constant; each needs its own oracle decomposition. +✅ (2026-07-17, 存量清扫 #7) frost-bomb TotalDPS 0.66x→1.00x:双根因 = +Archmage buff `DamageGainAsLightning`(80% per 100 Mana)未入玩家 buff +允收名单 + SkillType neg tag 不支持;以及 curse 链两缺口(EW 取数等级未吃 ++spell-skill levels、技能局部 CurseEffect 段缺失)。见 commit 9075077。 **#5 target — ailment (ignite) magnitude. ✅ DONE (one root cause: Blazing Critical global fire buff).** Re-triage first: the ailment *formula* did not @@ -229,6 +233,11 @@ Dot-side leftovers, each triaged **not** a 0.5.4b ailment item: `abyssal-lich` 0.94x / `pathfinder` 0.92x track their hit gaps. - `frost-bomb` dot 0.87x and `essence-drain` WithDotDPS 1.36x: goldens byte-identical across the flip — pre-existing gaps. + ✅ (2026-07-17, #7) essence-drain 1.36x→1.00x:数据管线漏下 + `GrantedEffectStatSets.RemoveStats`(社区 schema 名 `IgnoredStats`)—— + ED 的 DoT set 本应移除主 set 的击中伤害 stat,PoBR 幻影击中 120.87 DPS。 + 管线补列 + adapter 置零语义 + 55 效果重生成(commit 5edb741)。 + frost-bomb dot 随 #7-1 修复 0.87x→1.07x(@10% 带内)。 - `gemling` dot 1.10x over: oracle per-component shows fire stored 1.03x × stacks 1.05x × slight crit-chance overshoot — downstream of its small hit-side overestimates, no dot-side mechanism. @@ -240,6 +249,20 @@ moved 1.04x→1.10x with #4a (低血 EHP 口径——vendor 只在显式 `conditionLowLife` config 下 cap `LifeRecoverable`,PoBR 的 EHP 消费端 尚未对齐该分支). Each is its own oracle-guided investigation; not all are single formula constants (several are unmodeled unique/flask interactions). +✅ (2026-07-17, #7) 两项收口(commit 6bb0685): +- ritualist TotalEHP 1.10x→1.00x:LifeRecoverable 口径是误诊(两侧本就一致, + vendor 只在显式 config 下 cap);真根因 = 策展条目 `also_grants_guard` + (护符 "Also grants N Guard")vendor 根本不解析(oracle AnyGuard=False), + 幻影 +491 共享 Guard 池已摘除。twister maxhit 族随之 1.06x→0.97x。 +- wolf-pack:Life 1.11x→1.00x(Giant's Blood `HalvesLifeFromStrength` + 接消费端)、Armour 0.98x→1.00x(PerStat Spirit 分母改读最终池值 336 而非 + BASE 300)、Mana→0.99x + gemling Life/Mana/MaxHit 族→1.00x(overlay-common + 三色宝石阈值条目池名归一 MaximumLife/MaximumMana)。**未闭合余量** = + companion ally-mitigation 层:vendor 把每个 hit pool 扩到 + `pool/(1−CompanionAllyDamageMitigation 10%)`(上限 TotalCompanionLife + 2262,CalcDefence.lua:3567-3595 allies 层)——PoBR 无 companion actor, + maxhit 族诚实落在 ~0.82x(此前被幻影 Guard 掩蔽到 ~0.96x)。归 companion + 本体项目。 ✅ (2026-07-17) `check-buff-refs` 15-drift re-review done: the recorded hashes were pinned at vendor `2df5a743` (pre-0.21.0; never refreshed at a82a33b either), so every buff "drifted" purely because `doActorMisc` shifted ~+75 lines From 41f3b71ce4415d6440da6748d5692a41658a342a Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 17:06:31 +0800 Subject: [PATCH 39/74] fix(parity): allocate named jewel sockets granted via anoint (Zarokh's Gift) The blood-mage 0.880x / abyssal-lich 0.926x TotalDPS residual was ledgered as a Mageblood crit-family gap (missing INC CritChance 107 'Mageblood'). That pin was a stale symptom: the mageblood effects table mirrors all 14 vendor rows (CalcPerform.lua:66-142) and the per-duplicate implicit is already parsed by generated/special_vendor.json, so the 107 INC is present and oracle-exact. Per-source diff of oracle critModList vs the PoBR ModDb pinned the real root cause: both builds anoint their amulet with '{enchant}Allocates Zarokh's Gift', a named jewel socket node (0_5 tree 11184, isJewelSocket+sinister). Vendor allocates it via the ResolveGrantedPassiveNodes sockets-by-name fallback (PassiveSpec.lua:1106-1114); PoBR's granted-passive path matches Notables only and the jewel gate knew only the Voices sinister-count channel, so the socketed jewel was dropped whole (blood-mage: Pandemonium Ornament, CritChance INC 24 + CritMult INC 25/28 = exactly the 5.34-vs-5.87 CritMult delta). Fix: NAMED_SOCKETS_0_5 in xml_build.rs, scanning equipped items' text sections for 'Allocates ' and treating matching named sockets as allocated, same pinned-id pattern (and tree-version ceiling) as SINISTER_SOCKETS_0_5. Parity: blood-mage TotalDPS 0.880x->1.00x (CritChance 88.5->92.1, CritMult 5.34->5.87 golden-exact), abyssal-lich 0.926x->1.00x (ES/MaxHit/EHP ->1.00x). 18-build per-cell diff: only these two builds moved. Baselines: off 73->76 @5% / 75->76 @10%, dot 27->31 @5% / 31->33 @10%; def unchanged. --- crates/pobr-build/src/xml_build.rs | 24 ++++++++++++++++++- .../pobr-build/tests/parity/ninja_parity.rs | 24 +++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/crates/pobr-build/src/xml_build.rs b/crates/pobr-build/src/xml_build.rs index 5f84704c..14ed6e97 100644 --- a/crates/pobr-build/src/xml_build.rs +++ b/crates/pobr-build/src/xml_build.rs @@ -677,11 +677,33 @@ fn parse_items_and_slots( .flat_map(|it| it.implicit_texts.iter().chain(&it.modifier_texts)) .filter_map(|t| sinister_socket_alloc_count(t)) .sum(); - let sinister_allocated: std::collections::HashSet = SINISTER_SOCKETS_0_5 + let mut sinister_allocated: std::collections::HashSet = SINISTER_SOCKETS_0_5 .iter() .copied() .take(sinister_count) .collect(); + // 具名 jewel socket 的「Allocates <名>」授予(vendor PassiveSpec.lua:1106-1114 + // ResolveGrantedPassiveNodes 的 sockets 名匹配 fallback):amulet anoint + // `{enchant}Allocates Zarokh's Gift` 分配 socket 节点,socket 内珠宝随之入计。 + // ponytail: 0_5 树唯一具名 socket 就是 Zarokh's Gift(其余全叫 Sinister Jewel + // Socket,走上面的 Voices 计数通道);树版本再迭代新增具名 socket 时 parity + // 门禁会点名,届时改从树数据取名表。 + const NAMED_SOCKETS_0_5: [(&str, u32); 1] = [("zarokh's gift", 11184)]; + let equipped_texts = out.iter().flat_map(|(_, item)| { + item.implicit_texts + .iter() + .chain(&item.modifier_texts) + .chain(&item.enchant_texts) + }); + for text in equipped_texts { + if let Some(name) = text.trim().strip_prefix("Allocates ") + && let Some((_, node)) = NAMED_SOCKETS_0_5 + .iter() + .find(|(n, _)| name.trim().eq_ignore_ascii_case(n)) + { + sinister_allocated.insert(*node); + } + } let mut all_jewel_ids = jewel_ids; all_jewel_ids.extend( socket_items diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index ac09a2f7..2205f092 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -747,8 +747,20 @@ const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refract // act_int.lua:229-231)+ curse 链两缺口(EW 取数等级未吃 +8 spell skill // levels:-58→-66;技能局部 CurseEffect 段缺失:Heightened Curse +25 + // EW 品质 +10 → 敌抗 9→-7,oracle enemyMitigation 逐源钉值)。 -const BASELINE_OFF_HIT5: usize = 73; // #6 AvgDamage 族 + 存量 #7(frost-bomb/essence-drain)合并实测 73/80,超过 0.5.0 时代的 71(#6 单独 71;#7 单独 60;迁移基线 39) -const BASELINE_OFF_HIT10: usize = 75; // #6+#7 合并实测 75/80(#6 单独 73;#7 单独 66;迁移基线 47;0.5.0=74) +// **0.5.4b #8 Zarokh's Gift anoint socket 重记(off +3 @5% 73→76 / +1 @10% 75→76)**: +// #6 留下的「blood-mage 缺 `INC CritChance 107 'Mageblood'`」钉值实为下游症状: +// Mageblood 效果表与 `MagesLegacyEffect` 解析均已在位(逐 mod 对拍与 oracle 一致), +// 真根因 = 两 build 的 amulet anoint `{enchant}Allocates Zarokh's Gift` 未把具名 +// jewel socket 节点 11184 视为已分配(vendor PassiveSpec.lua:1106-1114 sockets 名 +// 匹配 fallback),socket 内珠宝(blood-mage: Pandemonium Ornament——CritChance +// INC 24 + CritMult INC 25/28;abyssal: 同位 ES/防御珠宝)整体被丢弃。修复见 +// xml_build.rs NAMED_SOCKETS_0_5。blood-mage TotalDPS 0.880x→1.00x(CritChance +// 88.5→92.1、CritMult 5.34→5.87 逐值精确)、abyssal-lich TotalDPS 0.926x→1.00x +// (CritChance/CritMult 1.00x;ES 12124→12437 vs golden 12434、MaxHit 五族 + +// TotalEHP 0.97-0.98x→1.00x——def 列先前已在 5% 带内,def 计数不变)。 +// 18 build 逐格 diff 仅此两 build 变动。 +const BASELINE_OFF_HIT5: usize = 76; // #8 Zarokh's Gift 后 76/80(#6+#7 合并 73;#6 单独 71;#7 单独 60;迁移基线 39) +const BASELINE_OFF_HIT10: usize = 76; // #8 后 76/80(#6+#7 合并 75;#6 单独 73;#7 单独 66;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -838,8 +850,12 @@ const BASELINE_OFF_HIT10: usize = 75; // #6+#7 合并实测 75/80(#6 单独 73 // TotalDotDPS 1.04x→1.06x 出 @5%(EW 变强的下游——vendor 侧 druid 的 EW // 根本未入 enemy resistMods(curse 槽位/优先级差异,oracle 钉值),PoBR // 施加了它,存量高估被放大 2%,另行追)。@5 净 0、@10 净 +2。 -const BASELINE_DOT_HIT5: usize = 27; // #6+#7 合并实测 27/37,超过 0.5.0 时代的 26(#6 单独 25;#7 单独 18;迁移基线 9) -const BASELINE_DOT_HIT10: usize = 31; // #6+#7 合并实测 31/37(#6 单独 27;#7 单独 25;迁移基线 11;0.5.0=28) +// **0.5.4b #8 Zarokh's Gift anoint socket 重记(dot +4 @5% 27→31 / +2 @10% 31→33)**: +// blood-mage TotalDotDPS 0.79x→1.00x + CombinedDPS 0.88x→1.00x、abyssal-lich +// TotalDotDPS 0.94x→1.00x + CombinedDPS 0.93x→1.00x——hit 侧 crit 闭合后 +// 点燃/DoT 基底跟正(见 BASELINE_OFF_HIT5 上 #8 说明)。 +const BASELINE_DOT_HIT5: usize = 31; // #8 后 31/37(#6+#7 合并 27;#6 单独 25;#7 单独 18;迁移基线 9) +const BASELINE_DOT_HIT10: usize = 33; // #8 后 33/37(#6+#7 合并 31;#6 单独 27;#7 单独 25;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 From 324e08e32e15a271b87462c4a1573ab376083d64 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 17:06:38 +0800 Subject: [PATCH 40/74] docs: record #8 outcome in adapting-to-0.5.4b (named-socket anoint root cause) --- docs/adapting-to-0.5.4b.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index f45f7d15..383e33d5 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -359,6 +359,39 @@ no `buff_definitions` entry content changed and parity is untouched. Line ranges re-pointed to the 0.22.0 (`ce8bffab`) locations, hashes refreshed via `--write`, `_meta.vendor_commit` updated; check now reports 0 drift. +**#8 target — the blood-mage/abyssal "Mageblood crit family" residual. ✅ DONE +(one root cause, and it was not Mageblood).** The #6 ledger entry ("PoBR missing +`INC CritChance 107 'Mageblood'`") turned out to be a stale symptom: line-by-line +diff of `calc/mageblood.rs` against vendor `CalcPerform.lua:66-142` shows the +effects table complete (all 14 legacies, Diamond included), and the +per-duplicate implicit (`all mage's legacies have (%d+)% increased effect...`, +ModParser.lua:5558 → `MagesLegacyEffect` INC) is already covered by +`generated/special_vendor.json` (`vnd_all_mage_s_legacies_have_d_...`). A +per-mod dump of the live build confirmed `CriticalStrikeChance INC 107 +src=Mageblood` present and oracle-exact. The *actual* gap, pinned by diffing +oracle `critModList`/`ORACLE_EXTRA_STATS` per-source against the PoBR ModDb: +both builds anoint their amulet with `{enchant}Allocates Zarokh's Gift` — a +*named jewel socket* node (0_5 tree 11184, `isJewelSocket` + `sinister`, alias +`DeliriumAnoint_ZarokhsGift_`). Vendor allocates it through the +`ResolveGrantedPassiveNodes` sockets-by-name fallback +(PassiveSpec.lua:1106-1114); PoBR's granted-passive path only matches Notables, +and the jewel gate in `xml_build.rs` only knew the Voices sinister-count +channel, so the socketed jewel was dropped whole (blood-mage: Pandemonium +Ornament — CritChance INC 24 + CritMult INC 25/28, exactly the CritMult +5.34-vs-5.87 delta; abyssal: an ES/defence jewel). Fix: +`NAMED_SOCKETS_0_5` in `xml_build.rs` — scan equipped items' three text +sections for `Allocates `, match named sockets (0_5 tree has exactly +one: Zarokh's Gift), and treat the node as allocated for jewel inclusion, +same pinned-id pattern (and same tree-version ceiling) as +`SINISTER_SOCKETS_0_5`. Results: blood-mage TotalDPS 0.880x→1.00x (CritChance +88.5→92.1, CritMult 5.34→5.87, both golden-exact), abyssal-lich TotalDPS +0.926x→1.00x (CritChance/CritMult 1.00x; ES 12124→12437 vs golden 12434, +MaxHit family + TotalEHP 0.97-0.98x→1.00x). 18-build per-cell diff: only these +two builds moved. Baselines: off 73→76 @5% / 75→76 @10%, dot 27→31 @5% / +31→33 @10%; def counts unchanged (the abyssal def cells were already inside +the 5% band). The other 7 Mageblood wearers: no movement — their legacy +modelling was already complete. + ## Tooling - `examples/demo-bd-test/tools/recapture_golden.py` — refresh fixture goldens From c7e74c20d9bb999473fc2df6e78c83a648af4a5f Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 17:37:37 +0800 Subject: [PATCH 41/74] data(parser): restore vendor 'skill speed' three-name fan-out Vendor ModParser.lua:770 maps the "skill speed" phrase to {Speed, WarcrySpeed, TotemPlacementSpeed}; the curated override in extract_parser_rules collapsed it to the single SkillSpeed bucket name, silently dropping the WarcrySpeed leg (tree "N% increased Skill Speed" nodes never reached warcry cast-time aggregation - 17 INC missing on the smith fixture, oracle-pinned per source). Restore the fan-out as {SkillSpeed, WarcrySpeed, TotemPlacementSpeed} and regen mod_parser_rules.json + parsed_mods.json for 4.5.4.3. The two extra names were inert until now (SCOPE_NAMED_INERT); the first real consumer is the warcry uptime machine (next commit). Parse coverage unchanged (3438/842). Mageblood's manual two-name injection and the statmap skill_speed entries already used this shape - all three channels now agree. --- data/4.5.4.3/generated/parsed_mods.json | 230 ++++++++++++++++++ data/4.5.4.3/overlay/mod_parser_rules.json | 4 +- .../src/extract_parser_rules.rs | 11 +- 3 files changed, 243 insertions(+), 2 deletions(-) diff --git a/data/4.5.4.3/generated/parsed_mods.json b/data/4.5.4.3/generated/parsed_mods.json index d23e3695..11d18e55 100644 --- a/data/4.5.4.3/generated/parsed_mods.json +++ b/data/4.5.4.3/generated/parsed_mods.json @@ -8543,6 +8543,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 10.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 10.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 10.0 } ] }, @@ -8557,6 +8567,22 @@ "tags": [ "Multiplier { var: \"RemovableFrenzyCharge\", div: 1.0, limit: Some(1.0), actor: None, limit_var: None, limit_actor: None, invert: false, limit_total: false }" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 10.0, + "tags": [ + "Multiplier { var: \"RemovableFrenzyCharge\", div: 1.0, limit: Some(1.0), actor: None, limit_var: None, limit_actor: None, invert: false, limit_total: false }" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 10.0, + "tags": [ + "Multiplier { var: \"RemovableFrenzyCharge\", div: 1.0, limit: Some(1.0), actor: None, limit_var: None, limit_actor: None, invert: false, limit_total: false }" + ] } ] }, @@ -8571,6 +8597,22 @@ "tags": [ "Condition { var: \"Shapeshifted\", negated: false, actor: None }" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 10.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 10.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] } ] }, @@ -13467,6 +13509,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 16.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 16.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 16.0 } ] }, @@ -14535,6 +14587,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 2.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 2.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 2.0 } ] }, @@ -14549,6 +14611,22 @@ "tags": [ "SkillTypes(SkillTypes(140737488355328))" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 2.0, + "tags": [ + "SkillTypes(SkillTypes(140737488355328))" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 2.0, + "tags": [ + "SkillTypes(SkillTypes(140737488355328))" + ] } ] }, @@ -20243,6 +20321,22 @@ "tags": [ "Condition { var: \"Shapeshifted\", negated: false, actor: None }" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 3.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 3.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] } ] }, @@ -20535,6 +20629,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 3.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 3.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 3.0 } ] }, @@ -20549,6 +20653,22 @@ "tags": [ "Condition { var: \"Shapeshifted\", negated: false, actor: None }" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 3.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 3.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] } ] }, @@ -20563,6 +20683,22 @@ "tags": [ "SkillTypes(SkillTypes(140737488355328))" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 3.0, + "tags": [ + "SkillTypes(SkillTypes(140737488355328))" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 3.0, + "tags": [ + "SkillTypes(SkillTypes(140737488355328))" + ] } ] }, @@ -20739,6 +20875,16 @@ "name": "SkillSpeed", "type": "Inc", "value": -3.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": -3.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": -3.0 } ] }, @@ -23434,6 +23580,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 4.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 4.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 4.0 } ] }, @@ -24449,6 +24605,22 @@ "tags": [ "Condition { var: \"OpenWeaknessEnemyPresence\", negated: false, actor: None }" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 40.0, + "tags": [ + "Condition { var: \"OpenWeaknessEnemyPresence\", negated: false, actor: None }" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 40.0, + "tags": [ + "Condition { var: \"OpenWeaknessEnemyPresence\", negated: false, actor: None }" + ] } ] }, @@ -25674,6 +25846,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 5.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 5.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 5.0 } ] }, @@ -25995,6 +26177,16 @@ "name": "SkillSpeed", "type": "Inc", "value": -5.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": -5.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": -5.0 } ] }, @@ -27679,6 +27871,16 @@ "name": "SkillSpeed", "type": "Inc", "value": 6.0 + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 6.0 + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 6.0 } ] }, @@ -29765,6 +29967,22 @@ "tags": [ "Condition { var: \"Shapeshifted\", negated: false, actor: None }" ] + }, + { + "name": "WarcrySpeed", + "type": "Inc", + "value": 8.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] + }, + { + "name": "TotemPlacementSpeed", + "type": "Inc", + "value": 8.0, + "tags": [ + "Condition { var: \"Shapeshifted\", negated: false, actor: None }" + ] } ] }, @@ -40350,6 +40568,18 @@ "type": "More", "value": 30.0, "kw": 16384 + }, + { + "name": "WarcrySpeed", + "type": "More", + "value": 30.0, + "kw": 16384 + }, + { + "name": "TotemPlacementSpeed", + "type": "More", + "value": 30.0, + "kw": 16384 } ] }, diff --git a/data/4.5.4.3/overlay/mod_parser_rules.json b/data/4.5.4.3/overlay/mod_parser_rules.json index 282f4397..02f8700a 100644 --- a/data/4.5.4.3/overlay/mod_parser_rules.json +++ b/data/4.5.4.3/overlay/mod_parser_rules.json @@ -5069,7 +5069,9 @@ { "phrase": "skill speed", "names": [ - "SkillSpeed" + "SkillSpeed", + "WarcrySpeed", + "TotemPlacementSpeed" ] }, { diff --git a/tools/sync-pob-catalog/src/extract_parser_rules.rs b/tools/sync-pob-catalog/src/extract_parser_rules.rs index 97500b78..ec7a8682 100644 --- a/tools/sync-pob-catalog/src/extract_parser_rules.rs +++ b/tools/sync-pob-catalog/src/extract_parser_rules.rs @@ -578,7 +578,16 @@ const AGGREGATE_EXPANSION: &[(&str, &[&str])] = &[ ("strength and intelligence", &["Strength", "Intelligence"]), ("strength and dexterity", &["Strength", "Dexterity"]), ("dexterity and intelligence", &["Dexterity", "Intelligence"]), - ("skill speed", &["SkillSpeed"]), + // vendor `["skill speed"] = { "Speed", "WarcrySpeed", "TotemPlacementSpeed" }` + // (ModParser.lua:770)。裸 `Speed` → PoBR 速度桶名 `SkillSpeed`;WarcrySpeed / + // TotemPlacementSpeed 保留原名扇出(存量 #9 起 WarcrySpeed 有真消费方 = + // `pobr-core::calc::warcry` 的喊叫时间,CalcOffence.lua:350-359; + // TotemPlacementSpeed 仍为惰性作用域名)。此前只落 `SkillSpeed` 单名, + // 「N% increased Skill Speed」文本对 warcry 喊叫速度静默丢失。 + ( + "skill speed", + &["SkillSpeed", "WarcrySpeed", "TotemPlacementSpeed"], + ), ]; /// 抽取自检(蓝图 §1.9):钉定 commit 下计数 / form id 集容差 0(Err); From a3bc3948317d15e9acac61989d32fc25256bf52f Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 17:37:56 +0800 Subject: [PATCH 42/74] feat(core): warcry uptime machine - Infernal Cry gain-as-fire (backlog #9) Model the offensive warcry uptime block (PoB2 CalcOffence.lua:3203-3256) generically for any warcry active skill, and wire its first consumer: Infernal Cry's uptime-scaled DamageGainAsFire. Mechanism, per vendor line references: - empowered-attack count: floor(min(WarcryPower, WarcryPowerCap) / WarcryPowerPer) + EmpoweredAttacks, x ExtraEmpoweredAttacks (CalcPerform.lua:2116-2130). WarcryPower BASE 20 comes from the enemy tier preset player_mods (ConfigOptions.lua:2007) - previously carried in enemy_presets.json but never injected into the player db; setup_env now injects them (data-driven, effective_only entries skipped). - cooldown: calcSkillCooldown (CalcOffence.lua:325-348) incl. the server-tick ceil when storedUses <= 1. - warcry cast time: calcWarcryCastTime (CalcOffence.lua:350-359), WarcryCastTime base seeded from the effect's cast_time. - uptime = min((empowers/Speed)/(cooldown+castTime), 1) x storedUses, capped 100% (CalcOffence.lua:3235-3237; the :3236 Lua precedence quirk is resolved to intent semantics like scaled_damage.rs :3845). - consumer: DamageGainAsFire BASE gain x uptime, ModFlag.Melee, gated on a Melee main skill (CalcPerform.lua:1362-1366 + CalcOffence.lua:3251-54). Plumbing: - new calc/warcry.rs runs in perform() before the hand passes, so the injected gain feeds both the hit and its derived ignite (vendor writes the skillModList before the damage section for the same reason). - offence::resolve_action_rate + hand_pass::hand_scope extracted so the warcry pass resolves the main-hand Speed bit-identically to the offence pass. - orchestrator warcry_skill_specs: fully data-driven spec assembly (own statmapped stats incl. quality, compatible support payloads via support_modifiers, gem level + support-granted levels - smith's Fire Mastery +1 makes the gain stat 62, oracle-exact). - stat_map_engine allowlist: WarcryPowerPer/Cap + InfernalExtraFireDamageMultiplier pass-through. Oracle-pinned (smith fixture): cooldown 6.27, castTime 0.5442176871, uptime 19.41163877%, injected gain 12.03521604 - all bit-exact vs the vendor 'Uptime Scaled Infernal Cry' mod; titan 15.99401294%/9.2765 ditto. Unit test pins the whole chain. --- .../pobr-build/src/calc_orchestrator/buffs.rs | 101 +++++ .../pobr-build/src/calc_orchestrator/mod.rs | 8 + crates/pobr-core/src/calc/env.rs | 9 + crates/pobr-core/src/calc/hand_pass.rs | 15 +- crates/pobr-core/src/calc/mod.rs | 3 + crates/pobr-core/src/calc/offence.rs | 55 ++- crates/pobr-core/src/calc/perform.rs | 7 + crates/pobr-core/src/calc/session.rs | 9 + crates/pobr-core/src/calc/setup_env.rs | 30 ++ crates/pobr-core/src/calc/warcry.rs | 403 ++++++++++++++++++ crates/pobr-core/src/rules/stat_map_engine.rs | 9 + 11 files changed, 625 insertions(+), 24 deletions(-) create mode 100644 crates/pobr-core/src/calc/warcry.rs diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index 05ce281d..6e356d8f 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -776,3 +776,104 @@ pub(crate) fn spirit_reservation_modifiers( } mods } + +/// (存量 #9)把所有**已启用 warcry 主动技能**构造为 [`pobr_core::calc::WarcrySpec`], +/// 经 `session.add_warcry_skill` 注入、由 pobr-core `calc::warcry`(perform 的 +/// hand pass 之前)按 uptime 缩放消费(vendor CalcOffence.lua:3203-3256 + +/// CalcPerform.lua:2116-2142;机制分解与 oracle 钉值见 warcry.rs 模块 doc)。 +/// +/// spec 装配(全部走既有数据通道,零技能硬编码): +/// - **skill-local mods** = 自身 statSet stat(含品质段)经 statmap +/// (`mapped_stat_modifiers`——Infernal 的 per-set +/// `infernal_cry_exerted_attack_all_damage_%_to_gain_as_fire_%` → +/// `InfernalExtraFireDamageMultiplier`、常量 stat `warcry_empowers_per_X_...` → +/// `WarcryPowerPer/Cap`)+ 组内**兼容 support** 载荷(`support_modifiers`, +/// 如 Cooldown Recovery II → `CooldownRecovery INC 30`)+ `WarcryCastTime BASE` +/// (效果 `cast_time`,对位 vendor skillModList 的 "Base" 条目, +/// CalcOffence.lua:351 的求和源)。 +/// - **取数等级** = 宝石等级 + 适用的 `+N to Level of ...`(`additional_gem_levels`, +/// vendor applyGemMods——smith 的 Infernal 21+1=22 级 gain 51 + 品质 trunc(0.5×23)=11 +/// → 62,oracle 逐值)。 +/// - cooldown / storedUses = granted_effect_levels 行(`resolve_skill_level`)。 +/// +/// 同一效果多组重复按 id 去重(vendor `not globalOutput.CryCalculated` 同责)。 +pub(crate) fn warcry_skill_specs( + build: &Build, + data: &BuildData, +) -> Vec { + use std::collections::HashSet; + let mut specs = Vec::new(); + let mut seen: HashSet<&str> = HashSet::new(); + for group in build.enabled_socket_groups() { + for gem in &group.gem_skills { + let Some(effect) = data.granted_effects.get(&gem.skill_id) else { + continue; + }; + if effect.is_support || !effect.skill_types.iter().any(|t| t == "Warcry") { + continue; + } + if !seen.insert(gem.skill_id.as_str()) { + continue; + } + // 全局 +N gem levels(applyGemMods)+ 同组兼容 support 授予等级 + // (smith 的 Fire Mastery `supported_fire_skill_gem_level_+` → Infernal + // 21→22 级,gain 51+q11=62 = oracle 逐值)。 + let level = gem.gem_level + + additional_gem_levels(build, data, &gem.skill_id) + + support_granted_gem_levels(build, data, &gem.skill_id); + let es = data.effect_stats(&gem.skill_id, level, gem.quality, gem.stat_set_index); + let set_key = data.selected_set_key(&gem.skill_id, gem.stat_set_index); + let stats: Vec = es.all().cloned().collect(); + if std::env::var("POBR_DBG_WARCRY").is_ok() { + eprintln!( + "[POBR_DBG_WARCRY] specs {} level={level} q={} set_key={set_key:?} stats={:?}", + gem.skill_id, + gem.quality, + stats + .iter() + .map(|s| (s.stat.as_str(), s.value)) + .collect::>() + ); + } + let mut mods = mapped_stat_modifiers( + &stats, + SourceKind::SkillGem, + &gem.skill_id, + &gem.skill_id, + set_key.as_deref(), + ); + mods.extend(support_modifiers(group, data, &gem.skill_id)); + if let Some(ms) = effect.cast_time { + mods.push( + Modifier::number("WarcryCastTime", ModType::Base, f64::from(ms) / 1000.0) + .with_source("Base"), + ); + } + let (cooldown_base_s, stored_uses) = data + .resolve_skill_level(&gem.skill_id, level) + .map(|r| { + ( + r.cooldown_s.unwrap_or(0.0), + // vendor `skillData.storedUses or 0`(CalcOffence.lua:3236)。 + r.stored_uses.map_or(0.0, f64::from), + ) + }) + .unwrap_or((0.0, 0.0)); + // warcry 键名(vendor CalcPerform.lua:2124 gsub 链:`" Cry"`/`"'s"`/空格 + // 全剥):"Infernal Cry" → `Infernal`。 + let name = buff_skill_name(data, &gem.skill_id) + .replace(" Cry", "") + .replace("'s", "") + .replace(' ', ""); + specs.push(pobr_core::calc::WarcrySpec { + name, + skill_id: gem.skill_id.clone(), + cooldown_base_s, + stored_uses, + skill_types: skill_type_bits(&effect.skill_types), + mods, + }); + } + } + specs +} diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index a1c90544..15e1d973 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -1393,6 +1393,14 @@ fn inject_buffs_and_heralds(session: &mut CalculationSession, build: &Build, dat session.add_buff_skill(spec); } + // 4b'''.(存量 #9)warcry 技能 → WarcrySpec 经 `session.add_warcry_skill` 注入, + // 消费在 pobr-core `calc::warcry`(perform 的 hand pass 之前):按 + // `min((賦能次数/主技能Speed)/(冷却+喊叫时间), 1)` 折算 uptime 后把 warcry + // 进攻效果(Infernal `DamageGainAsFire`)缩放注入(CalcOffence.lua:3203-3256)。 + for spec in warcry_skill_specs(build, data) { + session.add_warcry_skill(spec); + } + // 4b''.(M4-m)herald 在场计数/条件(vendor CalcPerform.lua:1792-1805, // mode_buffs 段——本编排路径恒置 mode_buffs=true):已启用组中 // skill_types 含 Herald 的主动技能按显示名去重 → `Multiplier:Herald` diff --git a/crates/pobr-core/src/calc/env.rs b/crates/pobr-core/src/calc/env.rs index e6bdb68c..784c87ef 100644 --- a/crates/pobr-core/src/calc/env.rs +++ b/crates/pobr-core/src/calc/env.rs @@ -26,6 +26,13 @@ pub struct Env { /// 且 minion buff 未落地,本波收在 `Env` 顶层(语义即玩家侧);T3 消费时如需 /// per-actor 再迁。**本阶段零消费**:空与否输出逐值不变。 pub buff_skills: Vec, + /// 玩家的 warcry 技能规格(存量 #9;`session::add_warcry_skill` 写入, + /// `perform` 在 hand pass 之前经 [`super::warcry::apply_warcry_uptime`] 消费—— + /// uptime 缩放后的 warcry 进攻效果(Infernal `DamageGainAsFire`)注入玩家 db)。 + pub warcry_skills: Vec, + /// warcry uptime 增益已注入(幂等防重,vendor `InfernalActive` flag 同责, + /// CalcPerform.lua:1365)。 + pub warcry_gain_injected: bool, /// keystone 名 → modifier 列表(M3 T0-4;`session::set_keystone_mods` 写入, /// T5 `merge_keystones`(env_finalize 阶段 1/5)消费)。**本阶段零消费**。 pub keystone_mods: BTreeMap>, @@ -73,6 +80,8 @@ impl Env { cfg: CalcConfig::attack().with_damage_type(DamageType::Physical), minions: Vec::new(), buff_skills: Vec::new(), + warcry_skills: Vec::new(), + warcry_gain_injected: false, keystone_mods: BTreeMap::new(), buff_definitions: Vec::new(), buff_handler_registry: Arc::new(HandlerRegistry::new()), diff --git a/crates/pobr-core/src/calc/hand_pass.rs b/crates/pobr-core/src/calc/hand_pass.rs index 92d06dc2..aed48601 100644 --- a/crates/pobr-core/src/calc/hand_pass.rs +++ b/crates/pobr-core/src/calc/hand_pass.rs @@ -194,6 +194,19 @@ fn run_single_pass( hand: &HandSource, input: &MinimalInput, ) -> MinimalOutput { + let (hand_cfg, hand_input) = hand_scope(hand, cfg, input); + calculate_minimal_vs_enemy(db, enemy_db, &hand_cfg, &hand_input) +} + +/// per-hand 计算域派生(武器基底注入 `MinimalInput` 副本 + per-hand 条件翻转 + +/// 武器位段替换)。`run_single_pass` 与 warcry uptime 预算(`calc::warcry` 需按 +/// 主手域解析 Speed,vendor CalcOffence.lua:3235 读同一 pass 的 +/// `globalOutput.Speed`)共用,保证两处速率逐位一致。 +pub(crate) fn hand_scope( + hand: &HandSource, + cfg: &CalcConfig, + input: &MinimalInput, +) -> (CalcConfig, MinimalInput) { let mut hand_input = *input; hand_input.base_hit_min += hand.weapon.hit_min; hand_input.base_hit_max += hand.weapon.hit_max; @@ -209,7 +222,7 @@ fn run_single_pass( // per-hand 武器位(W-B2):非空时替换 cfg 的武器位段为该手武器位 // (空 = 恒等,legacy 位表 / 非武器攻击 source 零行为)。 hand_cfg.flags = hand_cfg.flags.replace_weapon_flags(hand.weapon.flags); - calculate_minimal_vs_enemy(db, enemy_db, &hand_cfg, &hand_input) + (hand_cfg, hand_input) } /// combineStat:按 [`COMBINE_TABLE`] 合并两腿(vendor `:2451-2545` + 调用面)。 diff --git a/crates/pobr-core/src/calc/mod.rs b/crates/pobr-core/src/calc/mod.rs index c8297050..509a3bb0 100644 --- a/crates/pobr-core/src/calc/mod.rs +++ b/crates/pobr-core/src/calc/mod.rs @@ -32,6 +32,7 @@ pub mod stun; pub mod survivability; pub mod taken; pub mod trigger; +pub mod warcry; pub use actor::{Actor, ActorBaseStats}; pub use ailment::{ @@ -120,6 +121,8 @@ pub use taken::{ MitigationCtx, MitigationInputs, armour_applies_pct, build_mitigation_ctx, damage_shift_table, effective_applied_armour, taken_hit_from_damage, }; +pub use warcry::WarcrySpec; + pub use trigger::{ CwcTriggerRate, EnergyTriggerRate, RotationResult, RotationSkill, SocketedSpellInfo, TriggerCondition, TriggerRate, TriggerSourceStats, action_cooldown, calc_cwc_trigger_rate, diff --git a/crates/pobr-core/src/calc/offence.rs b/crates/pobr-core/src/calc/offence.rs index 002ec7b4..172f9731 100644 --- a/crates/pobr-core/src/calc/offence.rs +++ b/crates/pobr-core/src/calc/offence.rs @@ -236,6 +236,37 @@ pub fn calculate_minimal(db: &ModDb, cfg: &CalcConfig, input: &MinimalInput) -> calculate_minimal_vs_enemy(db, &ModDb::new(), cfg, input) } +/// 出手速率解析(= vendor `globalOutput.Speed`):速度族(按技能类型取 AttackSpeed 或 +/// CastSpeed,SkillSpeed 始终)作为一个 inc/more 乘区;ActionSpeed 独立乘区单独相乘 +/// (对齐 PoB CalcOffence:`finalRate = base × (1+Σinc/100) × Π(more) × ActionSpeedMod`)。 +/// 攻击吃武器攻速 + AttackSpeed,法术吃技能施法速率 + CastSpeed——不混淆。 +/// 速度 inc/more 缩放后,先按附加施放/攻击时间(TotalCastTime/TotalAttackTime)换算 +/// 有效时间,再乘 ActionSpeed 独立乘区(PoB CalcOffence L2827 的加法分母 + 末端 action +/// speed),最后冷却限速 + 服务器帧 cap。 +/// +/// 独立成函供两处共用:`calculate_minimal_vs_enemy` 主链,以及 warcry uptime 预算 +/// (`calc::warcry`——vendor 的 warcry 段读同一 `globalOutput.Speed`, +/// CalcOffence.lua:3235)。 +pub(crate) fn resolve_action_rate(db: &ModDb, cfg: &CalcConfig, input: &MinimalInput) -> f64 { + let speed_names = super::skill_use_time::speed_names_for(cfg); + let action_speed_names = [ModName::from(super::skill_use_time::ACTION_SPEED)]; + let inc_speed = db.sum(ModType::Inc, cfg, &speed_names); + let more_speed = db.more(cfg, &speed_names); + let action_speed_mod = (1.0 + db.sum(ModType::Inc, cfg, &action_speed_names) / 100.0) + * db.more(cfg, &action_speed_names); + let scaled_rate = apply_total_time( + db, + cfg, + input.base_action_rate * (1.0 + inc_speed / 100.0) * more_speed, + ); + let uncapped_action_rate = scaled_rate * action_speed_mod; + round(apply_server_tick_cap( + db, + cfg, + apply_cooldown_cap(db, cfg, uncapped_action_rate), + )) +} + /// 完整入口:玩家 modDB + 敌人 modDB。敌人侧减伤/受伤链/格挡仅在 /// `cfg.mode_effective == true` 时生效(面板口径不引入敌人交互,保证与历史输出一致)。 /// @@ -257,29 +288,7 @@ pub fn calculate_minimal_vs_enemy( let cold_resistance = cold.final_value; let lightning_resistance = lightning.final_value; - // 速度族(按技能类型取 AttackSpeed 或 CastSpeed,SkillSpeed 始终)作为一个 inc/more 乘区; - // ActionSpeed 独立乘区单独相乘(对齐 PoB CalcOffence: - // finalRate = base × (1+Σinc/100) × Π(more) × ActionSpeedMod)。攻击吃武器攻速 + AttackSpeed, - // 法术吃技能施法速率 + CastSpeed——不混淆(攻击不吃 CastSpeed、法术不吃 AttackSpeed)。 - let speed_names = super::skill_use_time::speed_names_for(cfg); - let action_speed_names = [ModName::from(super::skill_use_time::ACTION_SPEED)]; - let inc_speed = db.sum(ModType::Inc, cfg, &speed_names); - let more_speed = db.more(cfg, &speed_names); - let action_speed_mod = (1.0 + db.sum(ModType::Inc, cfg, &action_speed_names) / 100.0) - * db.more(cfg, &action_speed_names); - // 速度 inc/more 缩放后,先按附加施放/攻击时间(TotalCastTime/TotalAttackTime)换算有效时间, - // 再乘 ActionSpeed 独立乘区(对齐 PoB CalcOffence L2827 的加法分母 + 末端 action speed)。 - let scaled_rate = apply_total_time( - db, - cfg, - input.base_action_rate * (1.0 + inc_speed / 100.0) * more_speed, - ); - let uncapped_action_rate = scaled_rate * action_speed_mod; - let action_rate = round(apply_server_tick_cap( - db, - cfg, - apply_cooldown_cap(db, cfg, uncapped_action_rate), - )); + let action_rate = resolve_action_rate(db, cfg, input); let accuracy_names = [ModName::from("Accuracy")]; let accuracy = scaled_numeric_stat(db, cfg, input.base_accuracy, &accuracy_names); // PoE2 命中率(agent-docs/accuracy-and-enemy.md §二,§三): diff --git a/crates/pobr-core/src/calc/perform.rs b/crates/pobr-core/src/calc/perform.rs index 5dbf4ed0..eb8ef1db 100644 --- a/crates/pobr-core/src/calc/perform.rs +++ b/crates/pobr-core/src/calc/perform.rs @@ -123,6 +123,13 @@ pub fn perform(env: &mut Env) -> Result<(), CalcError> { env.cfg.multipliers.insert("Life".into(), life_pool); } + // warcry uptime 机器(存量 #9,vendor CalcOffence.lua:3203-3256):在 hand pass + // **之前**把 uptime 缩放后的 warcry 进攻效果(Infernal `DamageGainAsFire`)注入 + // 玩家 db——vendor 同样在伤害段之前写 skillModList,故击中与其派生 DoT(点燃) + // 都吃该增益。主技能 Speed 用与主手 pass 逐位一致的 resolve_action_rate 预解析 + // (速率是 (db,cfg,input) 的确定函数,注入 gain-as 不回馈速度,无自引用)。 + super::warcry::apply_warcry_uptime(env); + let mut input = MinimalInput::from(env.player.base); // 命中率的敌人闪避来源:优先用 enemy.mod_db 的 Evasion BASE(setup_env 注入,含档位倍率), // 回退到 enemy.base.evasion 标量(兼容直接构造 Env 的旧入口)。 diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index 6055a61c..c0ab8dfb 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -341,6 +341,15 @@ impl CalculationSession { self.env.buff_skills.push(spec); } + /// 注入一个 warcry 技能规格(存量 #9 warcry uptime 机器)。消费在 `perform` + /// 的 hand pass 之前([`super::warcry::apply_warcry_uptime`]):按 + /// `min((賦能次数/主技能Speed)/(冷却+喊叫时间), 1)` 折算 uptime 后,把 warcry + /// 的进攻性效果(Infernal Cry `DamageGainAsFire`)缩放注入玩家 db + /// (vendor CalcOffence.lua:3229-3256)。`cfg.mode_buffs` 门控。 + pub fn add_warcry_skill(&mut self, spec: super::warcry::WarcrySpec) { + self.env.warcry_skills.push(spec); + } + /// 接入一个召唤物(M5a-B2):从 [`MinionDef`](super::MinionDef) 真实底材 + 召唤 /// 宝石等级 + 数量上限派生召唤物 `Actor` 接入 `Env.minions`,并把上限写为玩家 /// `Multiplier:SummonedMinion` / `Multiplier:MinionPresenceCount`(供「per Minion」 diff --git a/crates/pobr-core/src/calc/setup_env.rs b/crates/pobr-core/src/calc/setup_env.rs index 6dfe89bb..3a93c9df 100644 --- a/crates/pobr-core/src/calc/setup_env.rs +++ b/crates/pobr-core/src/calc/setup_env.rs @@ -150,6 +150,36 @@ pub fn setup_enemy(env: &mut Env, config_level: u32, tier: EnemyTier) { inject_enemy_mods(&mut env.enemy.mod_db, &defaults, tier); inject_ehp_damage_placeholder(&mut env.enemy.mod_db, constants, defaults.level, tier); + // 档位预设的**玩家侧** mod 组(数据驱动:`enemy_presets.json::tiers[].player_mods`; + // vendor ConfigOptions.lua L2007-2008 等 `modList:NewMod("WarcryPower","BASE",20, + // "Boss")` + `Multiplier:EnemyPower`,Boss/Pinnacle/Uber 共通)。首个消费方 = + // warcry uptime 机器(`calc::warcry` 的 WarcryPower 求和,CalcPerform.lua:2120)。 + // effective_only 条目(当前 player_mods 无)按保守跳过,避免面板口径引入敌人交互。 + if let Some(preset) = env.cfg.constants.enemy_presets.tier_for(tier) { + let player_mods: Vec = preset + .player_mods + .iter() + .filter(|m| !m.effective_only) + .filter_map(|m| { + let mod_type = match m.mod_type.as_str() { + "BASE" => ModType::Base, + "INC" => ModType::Inc, + "MORE" => ModType::More, + _ => return None, // 未知类型:保守跳过(数据损坏防御)。 + }; + Some( + crate::Modifier::number(m.name.as_str(), mod_type, m.value) + .with_source(m.source_label.clone()) + .with_origin(ModifierSource::new(SourceId::new( + SourceKind::EnemyConfig, + format!("tier_player.{}.{}", preset.id, m.name), + ))), + ) + }) + .collect(); + env.player.mod_db.add_list(player_mods); + } + // 注意:Boss 自带元素穿透(Pinnacle 3 / Uber 8,vendor `pinnacleBossPen = 15/5` / // `uberBossPen = 40/5`,Modules/Data.lua:231/:233)**只作用在防御侧**—— // `enemy{Fire,Cold,Lightning}Pen` config var 没有 apply 函数(ConfigOptions.lua:2269-2273, diff --git a/crates/pobr-core/src/calc/warcry.rs b/crates/pobr-core/src/calc/warcry.rs new file mode 100644 index 00000000..dca94689 --- /dev/null +++ b/crates/pobr-core/src/calc/warcry.rs @@ -0,0 +1,403 @@ +//! Warcry uptime 机器(存量 #9):非主技能的 warcry 按「賦能攻擊次數 / 主技能出手 +//! 速率 / (冷却 + 喊叫时间)」折算 uptime,再把 warcry 的进攻性效果按 uptime 缩放 +//! 注入主技能聚合。 +//! +//! 逐段对照 vendor: +//! - **WarcryPower / 賦能次数**:`CalcPerform.lua:2116-2142`(Warcry buff 分支)—— +//! `warcryPower = Override("WarcryPower") or max(Sum(BASE)×(1+Sum(INC)/100), +//! Sum(BASE,"MinimumWarcryPower"))`(:2120);`baseEmpowers = +//! floor(min(power, Sum("WarcryPowerCap")) / Sum("WarcryPowerPer"))`(:2121-2123) +//! `+ Sum("EmpoweredAttacks")`(:2125);`totalEmpowers = +//! (base + Sum("ExtraEmpoweredAttacks")) × More("ExtraEmpoweredAttacks")` +//! (:2127-2129)→ 发布 `NumEmpowers`(:2130)。 +//! - **冷却**:`calcSkillCooldown`(CalcOffence.lua:325-348)——`cooldown = +//! Override("CooldownRecovery") or (skillData.cooldown + Sum(BASE)) / +//! max(0, (1+Sum(INC)/100)×More)`,storedUses ≤ 1 且无 AdditionalCooldownUses +//! 时向上取整到服务器帧(:338-346)。 +//! - **喊叫时间**:`calcWarcryCastTime`(CalcOffence.lua:350-359)—— +//! `1 / min((1/Sum(BASE,"WarcryCastTime")) × mod("WarcrySpeed") × +//! actionSpeedMod, ServerTickRate)`;`InstantWarcry` flag → 0。 +//! - **uptime**:CalcOffence.lua:3229-3237(Infernal 分支,Ancestral/Intimidating/ +//! Rallying 同构)——`baseUptimeRatio = min((NumEmpowers / Speed) / +//! (cooldown + castTime), 1) × 100`;`UptimeRatio = min(100, baseUptimeRatio × +//! storedUses)`。注:vendor `:3236` 的 `storedUses or 0 + Sum(...)` 受 Lua 优先级 +//! 影响实为 `storedUses or (0 + Sum)`,本实现按意图语义 +//! `storedUses + Sum("AdditionalCooldownUses")`(与 scaled_damage.rs `:3845` +//! 笔误处置同一先例;两 fixture 均 storedUses=1、Additional=0,取值相同)。 +//! - **Infernal 消费点**:`CalcPerform.lua:1362-1366` 把 warcry skillModList 的 +//! `InfernalExtraFireDamageMultiplier` 发布为玩家 `InfernalExtraFireDamage`; +//! `CalcOffence.lua:3251-3254` 注入 `DamageGainAsFire BASE gain×uptime/100` +//! (ModFlag.Melee,source "Uptime Scaled Infernal Cry")。本实现把两步折叠: +//! 直接从 spec 的 skill-local mods(+玩家 db)求和后按 uptime 注入。 +//! +//! 门控(vendor CalcOffence.lua:3203-3205/:3229):`env.mode_buffs`;主技能非 +//! NeverExertable/Triggered/OtherThingUsesSkill/Retaliation;Infernal 消费点要求 +//! 主技能带 SkillType.Melee。 +//! +//! ponytail: 本模块当前只接 Infernal 的 DamageGainAsFire 消费点(fixture 中唯一 +//! 消费者);Intimidating/Rallying/Seismic 的 exert 伤害乘区(OffensiveWarcryEffect +//! 族)待有消费 build 时按同一 spec/uptime 底座补。 + +use pobr_data::prelude::*; +use pobr_data::skill::SkillTypes; + +use crate::{CalcConfig, ModDb, Modifier}; + +use super::env::Env; +use super::offence::MinimalInput; + +/// 一个 warcry 主动技能的注入规格(编排层 `warcry_skill_specs` 构造, +/// `session::add_warcry_skill` 写入 `Env::warcry_skills`)。 +#[derive(Debug, Clone)] +pub struct WarcrySpec { + /// warcry 键名(vendor `buff.name:gsub(" Cry",""):gsub("'s",""):gsub(" ","")`, + /// CalcPerform.lua:2124——"Infernal Cry" → `Infernal`;`EmpoweredAttacks` + /// 求和键用)。 + pub name: String, + /// 授予效果 id(去重 + 归因)。 + pub skill_id: String, + /// 技能基础冷却(秒,granted_effect_levels `cooldown_ms`;vendor + /// `skillData.cooldown`)。 + pub cooldown_base_s: f64, + /// `skillData.storedUses`(granted_effect_levels `stored_uses`,缺省 1)。 + pub stored_uses: f64, + /// warcry 技能自身的类型位(skill-local/全局词条按 warcry 域匹配用—— + /// 对位 vendor 的 per-skill `skillCfg`)。 + pub skill_types: SkillTypes, + /// skill-local mod 列表 = 自身 statmap 产物(WarcryPowerPer/Cap、 + /// InfernalExtraFireDamageMultiplier 等)+ 组内兼容 support 载荷 + /// (如 Cooldown Recovery II 的 CooldownRecovery INC 30)+ + /// `WarcryCastTime BASE`(效果 cast_time,对位 vendor skillModList "Base" 条)。 + pub mods: Vec, +} + +/// spec-local 求和(vendor skillModList 的 local 段;global 段由玩家 db 单独求和后相加)。 +fn local_sum(mods: &[Modifier], cfg: &CalcConfig, mod_type: ModType, name: &str) -> f64 { + mods.iter() + .filter(|m| m.mod_type == mod_type && m.name.as_str() == name && m.matches(cfg)) + .filter_map(|m| m.effective_number(cfg)) + .sum() +} + +/// spec-local MORE 连乘(`Π(1+v/100)`)。 +fn local_more(mods: &[Modifier], cfg: &CalcConfig, name: &str) -> f64 { + mods.iter() + .filter(|m| m.mod_type == ModType::More && m.name.as_str() == name && m.matches(cfg)) + .filter_map(|m| m.effective_number(cfg)) + .fold(1.0, |acc, v| acc * (1.0 + v / 100.0)) +} + +/// skill-local + 玩家 db 的合并 BASE/INC 求和(vendor skillModList 链上溯 modDB)。 +fn scoped_sum(db: &ModDb, spec: &WarcrySpec, cfg: &CalcConfig, ty: ModType, name: &str) -> f64 { + db.sum(ty, cfg, &[ModName::from(name)]) + local_sum(&spec.mods, cfg, ty, name) +} + +fn scoped_more(db: &ModDb, spec: &WarcrySpec, cfg: &CalcConfig, name: &str) -> f64 { + db.more(cfg, &[ModName::from(name)]) * local_more(&spec.mods, cfg, name) +} + +/// 賦能攻擊次数(`NumEmpowers`,CalcPerform.lua:2116-2130)。 +fn total_empowers(db: &ModDb, spec: &WarcrySpec, cfg: &CalcConfig) -> f64 { + // :2120 —— WarcryPower:config OVERRIDE(multiplierWarcryPower)优先,否则 + // BASE×(1+INC/100) 与 MinimumWarcryPower 取大(BASE 20 来自敌人档位预设 + // player_mods,Boss/Pinnacle/Uber 共通,ConfigOptions.lua:2007)。 + let warcry_power = db + .override_(cfg, ModName::from("WarcryPower")) + .unwrap_or_else(|| { + let base = db.sum(ModType::Base, cfg, &[ModName::from("WarcryPower")]); + let inc = db.sum(ModType::Inc, cfg, &[ModName::from("WarcryPower")]); + let min = db.sum(ModType::Base, cfg, &[ModName::from("MinimumWarcryPower")]); + (base * (1.0 + inc / 100.0)).max(min) + }); + // :2121-2123 —— per/cap 是 skillModList 侧 stat(Infernal 常量 stat 10/50)。 + let power_cap = scoped_sum(db, spec, cfg, ModType::Base, "WarcryPowerCap"); + let power_per = scoped_sum(db, spec, cfg, ModType::Base, "WarcryPowerPer"); + let mut base_empowers = if power_per > 0.0 { + (warcry_power.min(power_cap) / power_per).floor() + } else { + 0.0 + }; + // :2125 —— `EmpoweredAttacks`(主技能 cfg 求和;fixture 无来源,恒 0)。 + base_empowers += scoped_sum( + db, + spec, + cfg, + ModType::Base, + &format!("{}EmpoweredAttacks", spec.name), + ); + if base_empowers <= 0.0 { + // :2126 —— base 为 0 时 vendor 不发布 NumEmpowers(Sum 取 0)。 + return 0.0; + } + // :2127-2129。 + let extra = scoped_sum(db, spec, cfg, ModType::Base, "ExtraEmpoweredAttacks"); + let mult = scoped_more(db, spec, cfg, "ExtraEmpoweredAttacks"); + (base_empowers + extra) * mult +} + +/// `calcSkillCooldown`(CalcOffence.lua:325-348)的 warcry 取值路径。 +/// 未建模:Temporalis 冷却注入(:328/:332-337)——fixture 无来源。 +fn actual_cooldown(db: &ModDb, spec: &WarcrySpec, cfg: &CalcConfig, tick_s: f64) -> f64 { + let name = "CooldownRecovery"; + let added = scoped_sum(db, spec, cfg, ModType::Base, name); + let base = spec.cooldown_base_s + added; + let recovery = (1.0 + scoped_sum(db, spec, cfg, ModType::Inc, name) / 100.0) + * scoped_more(db, spec, cfg, name); + let cooldown = match db.override_(cfg, ModName::from(name)) { + Some(v) => v, + None => base / recovery.max(0.0), + }; + // :338-346 —— 可存多次使用时不取整到服务器帧。 + let extra_uses = scoped_sum(db, spec, cfg, ModType::Base, "AdditionalCooldownUses"); + if spec.stored_uses > 1.0 || extra_uses > 0.0 { + cooldown + } else { + (cooldown / tick_s).ceil() * tick_s + } +} + +/// `calcWarcryCastTime`(CalcOffence.lua:350-359)。 +/// 未建模:`SupportedByAutoexertion`(:355 后半)——fixture 无来源。 +fn warcry_cast_time(db: &ModDb, spec: &WarcrySpec, cfg: &CalcConfig, tick_s: f64) -> f64 { + if db.flag(cfg, ModName::from("InstantWarcry")) { + return 0.0; + } + let base = scoped_sum(db, spec, cfg, ModType::Base, "WarcryCastTime"); + if base <= 0.0 { + return 0.0; // 无 cast time 数据(防御:除零)。 + } + // 只求和 "WarcrySpeed"(:352)。「N% increased Skill Speed」文本在 parser 侧 + // 扇出成 {SkillSpeed, WarcrySpeed, TotemPlacementSpeed}(vendor ModParser.lua:770 + // 同构;extract_parser_rules 的 curated 覆盖,存量 #9 起对齐),statmap 的 + // `skill_speed_+%` 条目与 mageblood.rs 的手工扇出本就双名——三通道口径一致, + // 此处单名求和无双计。 + let speed_mod = (1.0 + scoped_sum(db, spec, cfg, ModType::Inc, "WarcrySpeed") / 100.0) + * scoped_more(db, spec, cfg, "WarcrySpeed"); + // vendor calcs.actionSpeedMod(actor):与 offence 主链同名 ActionSpeed 乘区。 + let action_names = [ModName::from(super::skill_use_time::ACTION_SPEED)]; + let action_speed = + (1.0 + db.sum(ModType::Inc, cfg, &action_names) / 100.0) * db.more(cfg, &action_names); + let rate = ((1.0 / base) * speed_mod * action_speed).min(1.0 / tick_s); + 1.0 / rate +} + +/// 单个 warcry 的 uptime(百分比 0..=100,CalcOffence.lua:3231-3237)。 +fn uptime_ratio( + db: &ModDb, + spec: &WarcrySpec, + scope_cfg: &CalcConfig, + main_cfg: &CalcConfig, + speed: f64, +) -> f64 { + if speed <= 0.0 { + return 0.0; + } + // 賦能次数按主技能 cfg 求和(vendor :2125/:3234 读 env.player.mainSkill.skillCfg / + // env.modDB);per/cap 等 skill-local 常量不带条件,两 cfg 等价。 + let empowers = total_empowers(db, spec, main_cfg); + let tick_s = main_cfg.constants.game().server_tick_seconds; + let cooldown = actual_cooldown(db, spec, scope_cfg, tick_s); + let cast_time = warcry_cast_time(db, spec, scope_cfg, tick_s); + if cooldown + cast_time <= 0.0 { + return 0.0; + } + let base_ratio = ((empowers / speed) / (cooldown + cast_time)).min(1.0) * 100.0; + // :3236 —— storedUses 意图语义(模块 doc 的 Lua 优先级说明)。 + let stored = + spec.stored_uses + scoped_sum(db, spec, scope_cfg, ModType::Base, "AdditionalCooldownUses"); + (base_ratio * stored).min(100.0) +} + +/// perform 阶段入口:主技能 hand pass 之前调用,把 uptime 缩放后的 warcry 进攻 +/// 效果注入玩家 db(vendor 在 CalcOffence 伤害段之前写 skillModList,注入后 +/// 击中与其派生 DoT(点燃)同吃该增益——smith dot 缺口 = 命中比平方的根源)。 +/// +/// 幂等:`env.warcry_gain_injected` 防重复注入(vendor 以 `InfernalActive` flag +/// 防重,CalcPerform.lua:1365)。 +pub fn apply_warcry_uptime(env: &mut Env) { + if env.warcry_skills.is_empty() || env.warcry_gain_injected { + return; + } + // vendor CalcOffence.lua:3203 `if env.mode_buffs`。 + if !env.cfg.mode_buffs { + return; + } + // :3205 —— 主技能可被赋能(NeverExertable/Triggered/OtherThingUsesSkill/ + // Retaliation 之外)。 + let excluded = [ + "NeverExertable", + "Triggered", + "OtherThingUsesSkill", + "Retaliation", + ] + .iter() + .filter_map(|n| SkillTypes::from_pob2_name(n)) + .fold(SkillTypes::NONE, |acc, t| acc | t); + if env.cfg.skill_types.intersects(excluded) { + return; + } + // 主技能 Speed(vendor globalOutput.Speed,:3235):与 hand pass 主手域逐位 + // 一致地解析(双持时 vendor 逐 pass 计算 uptime——此处取主手,见 hand_scope doc)。 + let input = MinimalInput::from(env.player.base); + let speed = match env + .hand_sources + .iter() + .find(|h| matches!(h.label, crate::HandTag::MainHand | crate::HandTag::Single)) + .or(env.hand_sources.first()) + { + Some(hand) => { + let (cfg, input) = super::hand_pass::hand_scope(hand, &env.cfg, &input); + super::offence::resolve_action_rate(&env.player.mod_db, &cfg, &input) + } + None => super::offence::resolve_action_rate(&env.player.mod_db, &env.cfg, &input), + }; + + let dbg = std::env::var("POBR_DBG_WARCRY").is_ok(); + let mut gain_mods: Vec = Vec::new(); + for spec in &env.warcry_skills { + // per-skill scope cfg(vendor skillCfg):warcry 自身类型位;flags/keyword + // 清空(warcry 非攻击/法术,主技能的武器位不外溢到冷却/喊叫速度求和)。 + let scope_cfg = env + .cfg + .clone() + .with_skill_types(spec.skill_types) + .with_flags(ModFlags::NONE) + .with_keyword_flags(KeywordFlags::NONE); + let uptime = uptime_ratio(&env.player.mod_db, spec, &scope_cfg, &env.cfg, speed); + if dbg { + let tick_s = env.cfg.constants.game().server_tick_seconds; + eprintln!( + "[POBR_DBG_WARCRY] {} empowers={} cooldown={} castTime={} speed={speed} uptime={uptime} localMods={}", + spec.skill_id, + total_empowers(&env.player.mod_db, spec, &env.cfg), + actual_cooldown(&env.player.mod_db, spec, &scope_cfg, tick_s), + warcry_cast_time(&env.player.mod_db, spec, &scope_cfg, tick_s), + spec.mods.len(), + ); + for m in &spec.mods { + eprintln!( + "[POBR_DBG_WARCRY] local {:?} {:?} {:?}", + m.name, m.mod_type, m.value + ); + } + for m in env.player.mod_db.iter_mods() { + if matches!(m.name.as_str(), "WarcrySpeed" | "SkillSpeed") && m.matches(&scope_cfg) + { + eprintln!( + "[POBR_DBG_WARCRY] db {:?} {:?} {:?} src={:?}", + m.name, m.mod_type, m.value, m.source + ); + } + } + } + + // Infernal 消费点(CalcOffence.lua:3229/:3251-3254):主技能须带 Melee。 + let gain = scoped_sum( + &env.player.mod_db, + spec, + &scope_cfg, + ModType::Base, + "InfernalExtraFireDamageMultiplier", + ); + if gain > 0.0 && env.cfg.skill_types.intersects(SkillTypes::MELEE) { + // :3253 —— `Condition:WarcryMaxHit`(config)时按满 uptime。 + let uptime_used = if env + .player + .mod_db + .flag(&env.cfg, ModName::from("Condition:WarcryMaxHit")) + || env.cfg.condition("WarcryMaxHit") + { + 100.0 + } else { + uptime + }; + if uptime_used > 0.0 { + let origin = ModifierSource::new(SourceId::new( + SourceKind::SkillGem, + format!("warcry.{}.uptime_gain_as_fire", spec.skill_id), + )) + .with_raw_text(format!( + "Uptime Scaled Infernal Cry ({gain} x {uptime_used}%)" + )); + gain_mods.push( + Modifier::number( + "DamageGainAsFire", + ModType::Base, + gain * uptime_used / 100.0, + ) + .with_flags(ModFlags::MELEE) + .with_source("Uptime Scaled Infernal Cry") + .with_origin(origin), + ); + } + } + } + env.player.mod_db.add_list(gain_mods); + env.warcry_gain_injected = true; +} + +#[cfg(test)] +mod tests { + //! smith-of-kitava oracle 钉值链(tools/pob2-oracle,2026-07-17 复跑): + //! WarcryPower 20(Boss preset)→ empowers floor(min(20,50)/10)=2; + //! cooldown 8/(1+(30-12+10)/100)=6.25 → 帧取整 6.27;castTime + //! 1/((1/0.8)×1.47×1)=0.544218;Speed 1.512 → + //! uptime (2/1.512)/6.814218=19.4116%;gain 62 → DamageGainAsFire 12.0352。 + + use super::*; + use crate::CalcConfig; + + fn smith_spec() -> WarcrySpec { + WarcrySpec { + name: "Infernal".into(), + skill_id: "InfernalCryPlayer".into(), + cooldown_base_s: 8.0, + stored_uses: 1.0, + skill_types: SkillTypes::WARCRY, + mods: vec![ + Modifier::number("WarcryCastTime", ModType::Base, 0.8), + Modifier::number("WarcryPowerPer", ModType::Base, 10.0), + Modifier::number("WarcryPowerCap", ModType::Base, 50.0), + Modifier::number("InfernalExtraFireDamageMultiplier", ModType::Base, 62.0), + // Cooldown Recovery II support 载荷。 + Modifier::number("CooldownRecovery", ModType::Inc, 30.0), + ], + } + } + + fn smith_db() -> ModDb { + let mut db = ModDb::new(); + db.add_list([ + Modifier::number("WarcryPower", ModType::Base, 20.0).with_source("Boss"), + Modifier::number("CooldownRecovery", ModType::Inc, -12.0).with_source("Quest"), + Modifier::number("CooldownRecovery", ModType::Inc, 10.0).with_source("Rune"), + // Mageblood 30 + 树上「Skill Speed」小点 17(parser 扇出成 WarcrySpeed)。 + Modifier::number("WarcrySpeed", ModType::Inc, 47.0), + ]); + db + } + + #[test] + fn smith_uptime_matches_oracle() { + let db = smith_db(); + let spec = smith_spec(); + let cfg = CalcConfig::attack(); + let tick = cfg.constants.game().server_tick_seconds; + + assert_eq!(total_empowers(&db, &spec, &cfg), 2.0); + + let cd = actual_cooldown(&db, &spec, &cfg, tick); + assert!((cd - 6.27).abs() < 1e-9, "cooldown {cd} != 6.27"); + + let ct = warcry_cast_time(&db, &spec, &cfg, tick); + assert!((ct - 0.5442176870748299).abs() < 1e-12, "castTime {ct}"); + + let uptime = uptime_ratio(&db, &spec, &cfg, &cfg, 1.512); + assert!( + (uptime - 19.41163877).abs() < 1e-6, + "uptime {uptime} != 19.41163877" + ); + let gain = 62.0 * uptime / 100.0; + assert!((gain - 12.03521604).abs() < 1e-6, "gain {gain}"); + } +} diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index 43759893..d93aa8fb 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -1724,6 +1724,15 @@ pub fn translate_mod_name( // 消费方 = `calc::ailment::ailment_rate_mod`(CalcOffence.lua:5036 rateMod, // calcLib.mod 的 INC+MORE 两腿同名集)。 "BleedFaster" | "PoisonFaster" | "IgniteFaster" => base_name.to_string(), + // 存量 #9:warcry uptime 机器直通族(vendor `warcry_empowers_per_X_monster_power[_mp_cap]` + // → WarcryPowerPer/Cap(SkillStatMap.lua:608-613)、Infernal Cry per-set + // `infernal_cry_exerted_attack_all_damage_%_to_gain_as_fire_%` → + // InfernalExtraFireDamageMultiplier(act_str.lua:7729-7731))。消费方 = + // `calc::warcry`(賦能次数 CalcPerform.lua:2121-2123 + uptime 缩放的 + // DamageGainAsFire 注入 CalcOffence.lua:3251-3254)。 + "WarcryPowerPer" | "WarcryPowerCap" | "InfernalExtraFireDamageMultiplier" => { + base_name.to_string() + } // M4-K:异常持续时间——vendor 施加方词条名带 Enemy 前缀(作用于敌身上的 // debuff 时长,CalcOffence.lua:5037 durationMod 取 // `EnemyDuration`/`EnemyAilmentDuration`/`DamagingAilmentDuration`), From 0ad6918678f4826eaf25fe2d1e5aec6a996db5d8 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 17:38:08 +0800 Subject: [PATCH 43/74] test(parity): record warcry-uptime gains - dot @10% baseline 31->32 smith TotalDotDPS 0.40x -> 1.06x (enters the 10% band): the dot residual was exactly the un-modeled uptime-scaled Infernal Cry DamageGainAsFire. The warcry inputs are now bit-exact vs the oracle; the remaining +6% is smith's pre-existing +2% hit-side overestimate (previously masked by the missing gain, AverageDamage 0.996 -> 1.02) squared through ignite. titan moves in sympathy (TotalDotDPS 0.98 -> 1.01, TotalDPS 0.98 -> 1.05, both still in the 5% band); the other 16 builds are value-identical. def/off/panel aggregates unchanged. Mark the adapting-doc backlog entry delivered. --- crates/pobr-build/tests/parity/ninja_parity.rs | 11 ++++++++++- docs/adapting-to-0.5.4b.md | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index ac09a2f7..0973df13 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -838,8 +838,17 @@ const BASELINE_OFF_HIT10: usize = 75; // #6+#7 合并实测 75/80(#6 单独 73 // TotalDotDPS 1.04x→1.06x 出 @5%(EW 变强的下游——vendor 侧 druid 的 EW // 根本未入 enemy resistMods(curse 槽位/优先级差异,oracle 钉值),PoBR // 施加了它,存量高估被放大 2%,另行追)。@5 净 0、@10 净 +2。 +// **存量 #9 warcry uptime 机器重记(dot +1 @10% 31→32)**:Infernal Cry 的 +// uptime 缩放 `DamageGainAsFire`(CalcOffence.lua:3229-3256,pobr-core +// `calc::warcry`)落地,smith TotalDotDPS 0.40x→1.06x 入 @10% 带(uptime/ +// castTime/cooldown 对 oracle 逐位:19.4116%/0.544218/6.27;gain 62×uptime +// =12.035 与 vendor "Uptime Scaled Infernal Cry" 条 bit-exact)。残余 +6% = +// smith 命中侧原有 +2% 高估(此前被缺失的 gain 抵消遮蔽,AverageDamage +// 0.996→1.02)经点燃 ∝ 火源² 放大——命中侧存量项,非 warcry 机制。titan 随动: +// TotalDotDPS 0.98→1.01、TotalDPS 0.98→1.05(同为被遮蔽的原有高估暴露, +// 仍在 @5% 带内)。其余 16 build 无 warcry,逐值不变。 const BASELINE_DOT_HIT5: usize = 27; // #6+#7 合并实测 27/37,超过 0.5.0 时代的 26(#6 单独 25;#7 单独 18;迁移基线 9) -const BASELINE_DOT_HIT10: usize = 31; // #6+#7 合并实测 31/37(#6 单独 27;#7 单独 25;迁移基线 11;0.5.0=28) +const BASELINE_DOT_HIT10: usize = 32; // 存量 #9 warcry uptime 后实测 32/37(#6+#7 31;#6 单独 27;#7 单独 25;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index f45f7d15..9c96fc28 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -324,6 +324,18 @@ Remaining offence after #6, each triaged: a pre-existing warcry-uptime gap; needs the warcry buff machinery (duration/cooldown/cast-time of a non-main skill + WarcryPower config), registered for its own slice. + ✅ (2026-07-17, #9) warcry uptime 机器落地(pobr-core `calc::warcry` + + 编排层 `warcry_skill_specs`,全数据驱动零技能硬编码):smith TotalDotDPS + 0.40x→1.06x(@10% 带内)。uptime/castTime/cooldown 对 oracle 逐位 + (19.4116% / 0.544218 / 6.27),注入 gain 12.035 = vendor "Uptime Scaled + Infernal Cry" 条 bit-exact。连带修三处通道:敌人档位预设 player_mods + (WarcryPower BASE 20)此前未注入 player db;statmap 直通族补 + WarcryPowerPer/Cap + InfernalExtraFireDamageMultiplier;parser 「skill + speed」短语恢复 vendor 三名扇出(ModParser.lua:770 {Speed, WarcrySpeed, + TotemPlacementSpeed},此前 curated 收窄为单名致树上 17 INC WarcrySpeed + 丢失)。残余 +6% = smith 命中侧原有 +2% 高估(此前被缺失 gain 抵消遮蔽) + 经点燃平方放大;titan 同理暴露(TotalDPS 0.98→1.05,带内)。dot 基线 + @10% 31→32。 - `spirit-walker`/`monk-twister` last ~2%: "Barrage Repeats" MORE DPS (vendor `output.DpsMultiplier` via `calcLib.mod(..., "DPS")`) — repeat DPS bonus channel unwired. From 3d0613b9b031a8d6679ab1eb09f6b2357d69f075 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 17:51:50 +0800 Subject: [PATCH 44/74] test: align charm guard expectation and coverage baseline with #7 de-modeling Backlog sweep #7 removed the also_grants_guard curated entry (phantom Guard pool that vendor ModParser never parses). Two consumers still pinned the old modeled behavior and slipped through #7's targeted gate: the charm ingest test now asserts the guard line is loudly reported as unsupported (matching PoB2), and the parse-coverage ratchet baseline drops 0.803738 -> 0.803271 for the two corpus lines that honestly returned to unsupported. --- crates/pobr-core/tests/sources/item_source.rs | 14 +++++++++----- devs/ci/parse-coverage-baseline.json | 8 ++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/pobr-core/tests/sources/item_source.rs b/crates/pobr-core/tests/sources/item_source.rs index 7f1d3202..147fd85b 100644 --- a/crates/pobr-core/tests/sources/item_source.rs +++ b/crates/pobr-core/tests/sources/item_source.rs @@ -433,8 +433,11 @@ fn ingest_charm_wraps_mods_into_list_payload_with_flask_attribution() { /// 同样来自真实 build,静默忽略会让调用方误以为效果已经生效。 #[test] fn ingest_charm_parses_guard_and_possession_effects_via_engine() { - // 引擎 special 通道已建模这两行(GuardAbsorb* / SpiritPossessionOnUse)—— - // legacy 时代它们是 unmodeled unsupported,现应正常产 mod、不进 unsupported。 + // possession 行由引擎 special 通道建模(SpiritPossessionOnUse),正常产 mod。 + // guard 行(`Also grants N Guard`)曾被策展条目 also_grants_guard 建模,但 + // vendor ModParser 根本不解析它——对 PoB2 golden 是幻影 Guard 池(存量 #7 + // 已摘除,ritualist EHP 1.10x→1.00x)。与 PoB2 对齐后它回到「未建模 → + // 必须响亮进 unsupported 报告」的口径,静默忽略才是失败。 let charm = utility_item( "Thawing Charm", &["Used when you become Frozen"], @@ -446,9 +449,10 @@ fn ingest_charm_parses_guard_and_possession_effects_via_engine() { let ingest = ingest_flask_charm("Charm 1", &charm); - assert!( - ingest.unsupported.is_empty(), - "guard/possession 行应由引擎解析,实际 unsupported: {:?}", + assert_eq!( + ingest.unsupported, + vec!["Also grants 435 Guard".to_string()], + "guard 行未建模应上报(与 PoB2 口径一致)、possession 行应解析,实际 unsupported: {:?}", ingest.unsupported ); } diff --git a/devs/ci/parse-coverage-baseline.json b/devs/ci/parse-coverage-baseline.json index 76410785..b0fce27f 100644 --- a/devs/ci/parse-coverage-baseline.json +++ b/devs/ci/parse-coverage-baseline.json @@ -4,12 +4,12 @@ "purpose": "M6-T7 解析覆盖率棘轮基线(蓝图 §6.3)。PR 不得降低 coverage_ratio;升高时同 PR 更新本文件。", "regen": "cargo run -p precompile-mods -- --data data/4.5.4.3 --report,取 generated/parse-coverage.json::summary", "parser_engine": "scan_engine+special", - "note": "golden 4.5.4.3(vendor ce8bffab):special_vendor 全量抽取 + 策展移植后覆盖率 0.794962→0.801168;Mageblood `Legacy of X` handler 接入后 0.801168→0.803738(+11 行)。" + "note": "golden 4.5.4.3(vendor ce8bffab):special_vendor 全量抽取 + 策展移植后覆盖率 0.794962→0.801168;Mageblood `Legacy of X` handler 接入后 0.801168→0.803738(+11 行)。;存量 #7 摘除 also_grants_guard 幻影建模(vendor 不解析,2 行如实回到 unsupported)0.803738→0.803271" }, "patch": "4.5.4.3", "total": 4280, - "parsed": 3440, - "unsupported": 840, + "parsed": 3438, + "unsupported": 842, "err": 0, - "coverage_ratio": 0.803738 + "coverage_ratio": 0.803271 } From df7335429ea28804d1675f829343f0af57c8b761 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:03:26 +0800 Subject: [PATCH 45/74] fix(build): route meta-gem support halves into group support judgement Support judgement (judge_group_supports) only enumerated gems whose primary granted effect is a support. Meta gems like Blasphemy carry their support half in additionalGrantedEffectId1 (SupportBlasphemyPlayer), so its skill-local 'CurseEffect MORE -41' (level 19) never reached the curse effect multiplier. Temporal Chains applied at -13% expire-slower instead of vendor's -8% (mult 0.55 vs 0.3245 = 1.1 quality x 0.59 blasphemy x 0.5 boss), inflating ignite debuffDurationMult and TotalDotDPS by ~6% on Blasphemy builds. Mirror vendor CalcSetup gemList assembly: expand each gem to its granted-effect list (gem_effects overlay) and route every support=true effect through the four-stage judgement, carrying the effect id to all consumers (support stat injection, mana multiplier, curse local effect, exposure supports, player buff specs). Gem instance statSetIndex is only honoured for the primary effect. Oracle-pinned on druid-oracle-comet: IgniteDuration 4.5977 -> 4.3478 (= vendor), IgniteEffMult 0.6 both sides (Elemental Weakness was never double-applied; curse slot arbitration already matched vendor). Parity: dot 31/37 -> 33/37 @5% (druid TotalDotDPS 1.06x -> 1.00x, monk frost-bomb 1.07x -> 0.99x), defensive/offensive tables unchanged; curse slot occupancy per build unchanged (POBR_DBG_CURSE sweep). Also extends POBR_DBG_CURSE with per-entry effect breakdown and adds POBR_DBG_AILMENT for ailment pass intermediates. --- .../pobr-build/src/calc_orchestrator/buffs.rs | 47 +++--- .../src/calc_orchestrator/skill_resolve.rs | 12 +- .../src/calc_orchestrator/stat_map.rs | 31 ++-- .../src/calc_orchestrator/triggers.rs | 146 +++++++++++------- .../pobr-build/tests/parity/ninja_parity.rs | 10 +- crates/pobr-core/src/calc/buff_pass.rs | 13 ++ crates/pobr-core/src/calc/perform.rs | 17 ++ 7 files changed, 175 insertions(+), 101 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index 6e356d8f..56ca5f6b 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -370,13 +370,13 @@ fn curse_local_effect_scale( }; absorb(skill_id, curse_level, gem.quality, gem.stat_set_index); let judgement = super::triggers::judge_group_supports(group, data, skill_id); - for &i in &judgement.compatible { - let sup = &group.gem_skills[i]; + for sup in &judgement.compatible { + let host = &group.gem_skills[sup.gem_index]; absorb( - &sup.skill_id, - sup.gem_level, - sup.quality, - sup.stat_set_index, + &sup.effect_id, + host.gem_level, + host.quality, + sup.stat_set_index(group), ); } (inc, more) @@ -400,7 +400,7 @@ fn curse_local_effect_scale( pub(crate) fn support_buff_specs(build: &Build, data: &BuildData) -> Vec { use std::collections::HashSet; let mut specs = Vec::new(); - let mut seen: HashSet<&str> = HashSet::new(); + let mut seen: HashSet = HashSet::new(); for group in build.enabled_socket_groups() { // 组内已启用主动技能(效果已知且非 support)。含附加授予效果 // (overlay/gem_effects.json 外键;vendor 对 additionalGrantedEffectId1..N @@ -424,34 +424,33 @@ pub(crate) fn support_buff_specs(build: &Build, data: &BuildData) -> Vec = HashSet::new(); + let mut compatible: HashSet<(usize, String)> = HashSet::new(); for active_id in &active_ids { - for idx in judge_group_supports(group, data, active_id).compatible { - compatible.insert(idx); + for sup in judge_group_supports(group, data, active_id).compatible { + compatible.insert((sup.gem_index, sup.effect_id)); } } - let mut indices: Vec = compatible.into_iter().collect(); - indices.sort_unstable(); - for idx in indices { + let mut entries: Vec<(usize, String)> = compatible.into_iter().collect(); + entries.sort_unstable(); + for (idx, effect_id) in entries { let gem = &group.gem_skills[idx]; - if !seen.insert(gem.skill_id.as_str()) { + if !seen.insert(effect_id.clone()) { continue; } - let es = data.effect_stats( - &gem.skill_id, - gem.gem_level, - gem.quality, - gem.stat_set_index, - ); - let set_key = data.selected_set_key(&gem.skill_id, gem.stat_set_index); - let mods = player_buff_stat_modifiers(data, &es, &gem.skill_id, set_key.as_deref()); + // 附加授予的 support 半身不沿用宝石实例 statSetIndex(只对主效果有意义)。 + let set_index = (gem.skill_id == effect_id) + .then_some(gem.stat_set_index) + .flatten(); + let es = data.effect_stats(&effect_id, gem.gem_level, gem.quality, set_index); + let set_key = data.selected_set_key(&effect_id, set_index); + let mods = player_buff_stat_modifiers(data, &es, &effect_id, set_key.as_deref()); if mods.is_empty() { continue; } specs.push(BuffSpec { - name: buff_skill_name(data, &gem.skill_id), + name: buff_skill_name(data, &effect_id), kind: BuffKind::Buff, - skill_id: gem.skill_id.clone(), + skill_id: effect_id.clone(), mods, magnitude: 1.0, slot: group.slot.clone(), diff --git a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs index 8ecd1b4e..be89c126 100644 --- a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs +++ b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs @@ -350,13 +350,13 @@ pub(crate) fn support_granted_gem_levels(build: &Build, data: &BuildData, skill_ } let judgement = super::triggers::judge_group_supports(group, data, skill_id); let mut total = 0u32; - for &idx in &judgement.compatible { - let sup = &group.gem_skills[idx]; + for sup in &judgement.compatible { + let host = &group.gem_skills[sup.gem_index]; let stats = data.effect_stats( - &sup.skill_id, - sup.gem_level, - sup.quality, - sup.stat_set_index, + &sup.effect_id, + host.gem_level, + host.quality, + sup.stat_set_index(group), ); for s in &stats.base { let Some(rest) = s.stat.strip_prefix("supported_") else { diff --git a/crates/pobr-build/src/calc_orchestrator/stat_map.rs b/crates/pobr-build/src/calc_orchestrator/stat_map.rs index f94a9efd..057fb1d2 100644 --- a/crates/pobr-build/src/calc_orchestrator/stat_map.rs +++ b/crates/pobr-build/src/calc_orchestrator/stat_map.rs @@ -418,7 +418,7 @@ pub(crate) fn exposure_support_modifiers( } // 曝光源宿主:组内主动技能自身产 debuff 曝光载荷,或(M4-m)自身/兼容 // support 含曝光施加能力载荷 → 其兼容 support 名单。 - let mut support_indices: BTreeSet = BTreeSet::new(); + let mut support_entries: BTreeSet<(usize, String)> = BTreeSet::new(); for gem in &group.gem_skills { let Some(effect) = data.granted_effects.get(&gem.skill_id) else { continue; @@ -436,32 +436,35 @@ pub(crate) fn exposure_support_modifiers( let judgement = judge_group_supports(group, data, &gem.skill_id); let is_host = has_debuff_payload(data, &es, &gem.skill_id, set_key.as_deref()) || has_exposure_inflict_stats(data, &es, &gem.skill_id, set_key.as_deref()) - || judgement.compatible.iter().any(|&idx| { - let sup = &group.gem_skills[idx]; + || judgement.compatible.iter().any(|sup| { + let host = &group.gem_skills[sup.gem_index]; // quality 传 0 与 support_modifiers 同口径。 - let sup_stats = - data.effect_stats(&sup.skill_id, sup.gem_level, 0, sup.stat_set_index); - let sup_key = data.selected_set_key(&sup.skill_id, sup.stat_set_index); - has_exposure_inflict_stats(data, &sup_stats, &sup.skill_id, sup_key.as_deref()) + let set_index = sup.stat_set_index(group); + let sup_stats = data.effect_stats(&sup.effect_id, host.gem_level, 0, set_index); + let sup_key = data.selected_set_key(&sup.effect_id, set_index); + has_exposure_inflict_stats(data, &sup_stats, &sup.effect_id, sup_key.as_deref()) }); if !is_host { continue; } - for idx in judgement.compatible { - support_indices.insert(idx); + for sup in judgement.compatible { + support_entries.insert((sup.gem_index, sup.effect_id)); } } - for idx in support_indices { + for (idx, effect_id) in support_entries { let gem = &group.gem_skills[idx]; + let set_index = (gem.skill_id == effect_id) + .then_some(gem.stat_set_index) + .flatten(); // quality 传 0 与 support_modifiers 同口径(support 品质表条目不存在)。 - let stats = data.effect_stats(&gem.skill_id, gem.gem_level, 0, gem.stat_set_index); - let set_key = data.selected_set_key(&gem.skill_id, gem.stat_set_index); + let stats = data.effect_stats(&effect_id, gem.gem_level, 0, set_index); + let set_key = data.selected_set_key(&effect_id, set_index); mods.extend( mapped_stat_modifiers( &stats.base, SourceKind::SupportGem, - &gem.skill_id, - &gem.skill_id, + &effect_id, + &effect_id, set_key.as_deref(), ) .into_iter() diff --git a/crates/pobr-build/src/calc_orchestrator/triggers.rs b/crates/pobr-build/src/calc_orchestrator/triggers.rs index e598b333..2c2d074e 100644 --- a/crates/pobr-build/src/calc_orchestrator/triggers.rs +++ b/crates/pobr-build/src/calc_orchestrator/triggers.rs @@ -550,17 +550,42 @@ pub(crate) fn in_group_trigger_source_stats( /// 组级 support 适用性裁决结果(M1 蓝图契约 C2)。 /// -/// `compatible` 为 `group.gem_skills` 中**通过 PoB2 四段裁决**的 support 下标(保持 -/// 插槽顺序);`final_skill_types` 为 addSkillTypes 不动点收敛后的主动技能类型集合 +/// `compatible` 为**通过 PoB2 四段裁决**的 support 效果引用(保持插槽顺序); +/// `final_skill_types` 为 addSkillTypes 不动点收敛后的主动技能类型集合 /// (种子 = 主动效果 `skill_types`,并入所有兼容 support 的 `add_skill_types`)。 #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct GroupSupportJudgement { - /// 兼容 support 在 `gem_skills` 中的下标(插槽顺序)。 - pub(crate) compatible: Vec, + /// 兼容 support(插槽顺序;含附加授予的 support 半身,见 [`CompatibleSupport`])。 + pub(crate) compatible: Vec, /// 不动点收敛后的技能类型集合(供后续 require 裁决 / T4 SupportManaMultiplier 复用)。 pub(crate) final_skill_types: std::collections::HashSet, } +/// 一个兼容 support 的效果引用:等级/品质/插槽序取宿主宝石实例(`gem_index`), +/// stat 取数用 `effect_id`——普通 support 二者同源(effect_id = 宝石主效果); +/// meta 宝石(Blasphemy)主效果是主动技能,support 半身在附加授予位 +/// (`gem_effects` 外键 `additionalGrantedEffectId1..N`,vendor 对 +/// grantedEffectList 逐效果按 `support` 标志分流,CalcSetup.lua gemList 装配)。 +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct CompatibleSupport { + /// 宿主宝石在 `group.gem_skills` 中的下标。 + pub(crate) gem_index: usize, + /// support 授予效果 id(stat / manaMultiplier / set_key 取数键)。 + pub(crate) effect_id: String, +} + +impl CompatibleSupport { + /// 该 support 效果的 statSet 选择:宝石实例的 `statSetIndex` 只对**主效果** + /// 有意义;附加授予的 support 半身用默认 set(vendor 附加效果同 gemInstance + /// 但 set 选择不跨效果沿用)。 + pub(crate) fn stat_set_index(&self, group: &SocketGroup) -> Option { + let gem = &group.gem_skills[self.gem_index]; + (gem.skill_id == self.effect_id) + .then_some(gem.stat_set_index) + .flatten() + } +} + /// 对一个 socket group 做 **support 适用性裁决 + addSkillTypes 不动点** /// (对照 PoB2 `Modules/CalcActiveSkill.lua:179-210`,契约 C2): /// @@ -591,42 +616,49 @@ pub(crate) fn judge_group_supports( .unwrap_or_default(); let cannot_be_supported = active_effect.is_some_and(|e| e.cannot_be_supported); - // 组内 support 下标(保持插槽顺序);active / 未知效果不参与裁决。 - let support_indices: Vec = group + // 组内 support 候选(保持插槽顺序):每个宝石的主授予效果 + 附加授予效果 + // (`gem_effects` 外键)中 `is_support` 者——vendor 对 grantedEffectList 逐 + // 效果按 support 标志分流,meta 宝石(Blasphemy)的 support 半身在附加位 + // (SupportBlasphemyPlayer,携带 `CurseEffect MORE` 等技能局部段)。 + // active / 未知效果不参与裁决。 + let support_candidates: Vec<(usize, &str)> = group .gem_skills .iter() .enumerate() - .filter(|(_, g)| { - data.granted_effects - .get(&g.skill_id) - .is_some_and(|e| e.is_support) + .flat_map(|(i, g)| { + std::iter::once(g.skill_id.as_str()) + .chain( + data.gem_effects + .get(&g.skill_id) + .into_iter() + .flat_map(|l| l.additional_granted_effect_ids.iter().map(String::as_str)), + ) + .filter(|id| data.granted_effects.get(*id).is_some_and(|e| e.is_support)) + .map(move |id| (i, id)) }) - .map(|(i, _)| i) .collect(); // 四段裁决(CalcTools.lua:84-110):cannotBeSupported → supportGemsOnly → // exclude 表达式 → require 表达式(空 = 接受)。socket group 内技能恒由宝石授予 // (from_gem=true);fromItem 特例 M5c、minionTypes 第二集合 M5a(defer)。 - let judge = |idx: usize, types: &HashSet| -> bool { - data.granted_effects - .get(&group.gem_skills[idx].skill_id) - .is_some_and(|effect| { - can_support( - &SupportJudgeInput { - support_gems_only: effect.support_gems_only, - exclude_skill_types: &effect.exclude_skill_types, - require_skill_types: &effect.require_skill_types, - }, - &ActiveSkillJudgeInput { - cannot_be_supported, - from_gem: true, - skill_types: types, - }, - ) - }) + let judge = |effect_id: &str, types: &HashSet| -> bool { + data.granted_effects.get(effect_id).is_some_and(|effect| { + can_support( + &SupportJudgeInput { + support_gems_only: effect.support_gems_only, + exclude_skill_types: &effect.exclude_skill_types, + require_skill_types: &effect.require_skill_types, + }, + &ActiveSkillJudgeInput { + cannot_be_supported, + from_gem: true, + skill_types: types, + }, + ) + }) }; - let merge_add = |idx: usize, types: &mut HashSet| { - if let Some(effect) = data.granted_effects.get(&group.gem_skills[idx].skill_id) { + let merge_add = |effect_id: &str, types: &mut HashSet| { + if let Some(effect) = data.granted_effects.get(effect_id) { for t in &effect.add_skill_types { types.insert(t.clone()); } @@ -634,24 +666,24 @@ pub(crate) fn judge_group_supports( }; // pass1:兼容 support 并入 addSkillTypes;不兼容进被拒名单。 - let mut rejected: Vec = Vec::new(); - for &i in &support_indices { - if judge(i, &skill_types) { - merge_add(i, &mut skill_types); + let mut rejected: Vec<&(usize, &str)> = Vec::new(); + for cand in &support_candidates { + if judge(cand.1, &skill_types) { + merge_add(cand.1, &mut skill_types); } else { - rejected.push(i); + rejected.push(cand); } } // repeat-until 不动点:重扫被拒名单直到一轮无新增。 loop { let mut newly_accepted = false; let mut still_rejected = Vec::with_capacity(rejected.len()); - for &i in &rejected { - if judge(i, &skill_types) { + for cand in rejected { + if judge(cand.1, &skill_types) { newly_accepted = true; - merge_add(i, &mut skill_types); + merge_add(cand.1, &mut skill_types); } else { - still_rejected.push(i); + still_rejected.push(cand); } } rejected = still_rejected; @@ -660,9 +692,13 @@ pub(crate) fn judge_group_supports( } } // pass2:终态集合下全量重裁决。 - let compatible: Vec = support_indices - .into_iter() - .filter(|&i| judge(i, &skill_types)) + let compatible: Vec = support_candidates + .iter() + .filter(|(_, id)| judge(id, &skill_types)) + .map(|&(i, id)| CompatibleSupport { + gem_index: i, + effect_id: id.to_string(), + }) .collect(); GroupSupportJudgement { compatible, @@ -688,20 +724,21 @@ pub(crate) fn support_modifiers( ) -> Vec { let judgement = judge_group_supports(group, data, active_skill_id); let mut mods = Vec::new(); - for &i in &judgement.compatible { - let gem = &group.gem_skills[i]; + for sup in &judgement.compatible { + let gem = &group.gem_skills[sup.gem_index]; + let set_index = sup.stat_set_index(group); // TODO(T1,T3.6 合并后 rebase 追加):quality 传参改为 gem.quality——support 的 // 品质表条目不存在(PoB2 导出即跳过),当前恒空段,传 0 与传 gem.quality 等价。 - let stats = data.effect_stats(&gem.skill_id, gem.gem_level, 0, gem.stat_set_index); + let stats = data.effect_stats(&sup.effect_id, gem.gem_level, 0, set_index); // support 的 set_key 取自身选中 set(per-set 覆盖以 support 效果 id 定位)。 // 注:vendor 对 support 效果不传 statSet(CalcActiveSkill.lua:130 全 set 全量 // merge)——多 set support 的附加 set 全量 merge 当前缺口见 m1 验收报告。 - let set_key = data.selected_set_key(&gem.skill_id, gem.stat_set_index); + let set_key = data.selected_set_key(&sup.effect_id, set_index); mods.extend(mapped_stat_modifiers( &stats.base, SourceKind::SupportGem, - &gem.skill_id, - &gem.skill_id, + &sup.effect_id, + &sup.effect_id, set_key.as_deref(), )); // (M1-T4.4)兼容 support 的分等级 cost 倍率 → `SupportManaMultiplier` MORE @@ -711,7 +748,7 @@ pub(crate) fn support_modifiers( // (倍率连乘截断 4 位小数后,先于 inc/more 链作用于 base cost)。 if let Some(mm) = data .granted_effect_levels - .get(&gem.skill_id) + .get(&sup.effect_id) .and_then(|rows| { rows.iter() .rfind(|r| r.level <= gem.gem_level) @@ -722,9 +759,9 @@ pub(crate) fn support_modifiers( { let origin = ModifierSource::new(SourceId::new( SourceKind::SupportGem, - format!("support.{}.manaMultiplier", gem.skill_id), + format!("support.{}.manaMultiplier", sup.effect_id), )) - .with_raw_text(format!("support {} cost multiplier {mm}%", gem.skill_id)); + .with_raw_text(format!("support {} cost multiplier {mm}%", sup.effect_id)); mods.push( Modifier::number("SupportManaMultiplier", ModType::More, mm).with_origin(origin), ); @@ -795,11 +832,8 @@ mod support_judgement_tests { } /// 兼容名单换算回效果 id(断言可读性)。 - fn compatible_ids(j: &GroupSupportJudgement, gem_order: &[&str]) -> Vec { - j.compatible - .iter() - .map(|&i| gem_order[i].to_string()) - .collect() + fn compatible_ids(j: &GroupSupportJudgement, _gem_order: &[&str]) -> Vec { + j.compatible.iter().map(|c| c.effect_id.clone()).collect() } /// 不动点顺序无关(CalcActiveSkill.lua:193-208):「A 加 Triggered、B require diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 1010ba53..4515f27d 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -864,7 +864,15 @@ const BASELINE_OFF_HIT10: usize = 76; // #8 后 76/80(#6+#7 合并 75;#6 单 // 0.996→1.02)经点燃 ∝ 火源² 放大——命中侧存量项,非 warcry 机制。titan 随动: // TotalDotDPS 0.98→1.01、TotalDPS 0.98→1.05(同为被遮蔽的原有高估暴露, // 仍在 @5% 带内)。其余 16 build 无 warcry,逐值不变。 -const BASELINE_DOT_HIT5: usize = 31; // #8+#9 合并实测 31/37(#8 单独 31;#9 单独 27;迁移基线 9;0.5.0=26) +// **存量 #11 Blasphemy support 半身接入(dot +2 @5% 31→33)**:support 裁决 +// 扩到宝石附加授予效果(gem_effects 外键),Blasphemy 的 SupportBlasphemyPlayer +// (`support_blasphemy_curse_effect_+%_final` → `CurseEffect MORE -41`@L19)进入 +// curse 局部乘区——Temporal Chains 施加值 -13→-8(vendor 逐位:mult 0.55→0.3245, +// (1+10%)×0.59×0.5boss),点燃 debuffDurationMult 1/0.87→1/0.92 跟正:druid +// TotalDotDPS 1.06x→1.00x(182.40 vs 182.60)、monk frost-bomb 1.07x→0.99x +// (4.53 vs 4.58);witch-lich 带内微降(21659→21621,仍 1.00x)。curse 槽位 +// 归属逐 build 不变(druid 单槽仍 Temporal Chains 胜出,EW 依旧不入槽)。 +const BASELINE_DOT_HIT5: usize = 33; // 存量 #11 后 33/37(#8+#9 合并 31;#8 单独 31;#9 单独 27;迁移基线 9;0.5.0=26) const BASELINE_DOT_HIT10: usize = 34; // #8+#9 合并实测 34/37(#8 单独 33;#9 单独 32;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 diff --git a/crates/pobr-core/src/calc/buff_pass.rs b/crates/pobr-core/src/calc/buff_pass.rs index 14353944..526dee28 100644 --- a/crates/pobr-core/src/calc/buff_pass.rs +++ b/crates/pobr-core/src/calc/buff_pass.rs @@ -400,6 +400,19 @@ pub fn buff_pass(env: &mut Env) { more *= env.enemy.mod_db.more(&env.cfg, &curse_effect_on_self); } let mult = (1.0 + inc / 100.0) * more; + if std::env::var("POBR_DBG_CURSE").is_ok() { + eprintln!( + "[POBR_CURSE] entry name={} inc={inc:.2} (local_inc={:.2}) more={more:.4} (local_more={:.4} magnitude={:.4}) mult={mult:.4} mods={:?}", + spec.name, + spec.local_effect_inc, + spec.local_effect_more, + spec.magnitude, + spec.mods + .iter() + .map(|m| (&m.name, &m.value)) + .collect::>(), + ); + } let source_id = format!("curse.{}", spec.skill_id); let mods = spec .mods diff --git a/crates/pobr-core/src/calc/perform.rs b/crates/pobr-core/src/calc/perform.rs index eb8ef1db..17883205 100644 --- a/crates/pobr-core/src/calc/perform.rs +++ b/crates/pobr-core/src/calc/perform.rs @@ -1045,12 +1045,29 @@ fn damaging_ailment_for_pass( ctx.speed, ); let sp = stack_potential(&stack); + if std::env::var("POBR_DBG_AILMENT").is_ok() { + eprintln!( + "[POBR_AILMENT] {name}: hit50={hit50:.2} crit50={crit50:.2} probe_chance={:.4} duration={:.4} speed={:.4} hit_chance={:.4} active={:.4} max={} sp={sp:.4}", + probe_out.chance, + ailment_duration(kind, player, cfg) * debuff_duration_mult(enemy, cfg), + ctx.speed, + ctx.hit_chance, + stack.active_stacks, + stack.max_stacks, + ); + } let ailment_crit = ailment_crit_chance(ctx.crit_chance, sp); let roll = roll_average(&stack); // Pass 2:高 roll 来源 + over-stacking 暴击 → 最终 magnitude。 let (hit_rolled, crit_rolled) = stored_source_at_roll(kind, &ctx.ranges, player, cfg, roll); let source = make_source(hit_rolled, crit_rolled, ailment_crit); let (out, _) = run(&source, trace); + if std::env::var("POBR_DBG_AILMENT").is_ok() { + eprintln!( + "[POBR_AILMENT] {name}: roll={roll:.2} hit_rolled={hit_rolled:.2} crit_rolled={crit_rolled:.2} ailment_crit={ailment_crit:.4} chance={:.4} eff_mult={:.4} magnitude_dps={:.4} duration={:.4}", + out.chance, out.eff_mult, out.magnitude_dps, out.duration_secs, + ); + } if stack.active_stacks > 0.0 { // vendor uptime 口径(`:5189-5193`):activeAilments = min(stacks, max)。 From b0eaadd49b79cf3008a29966b6046d66259a32c8 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:09:15 +0800 Subject: [PATCH 46/74] test(parity): MARTIAL ES 1.12x is a stale 0.1-era embedded golden, not an over-count Feeding the decoded MARTIAL XML (targetVersion 0_1) to the current vendor oracle yields EnergyShield 7008; PoBR computes 7005.5 (0.9996x). Every era-stable stat (Life/Mana/Evasion/resists/crit) matches the embedded PlayerStats exactly - only ES and offence drifted with 0.5.x data, so the 1.12x vs the embedded 6257 is golden staleness, same class as the deadeye Mageblood/res notes in this file. Document the diagnosis inline and add guards: wide 0.15 tolerance on ES (covers the stale-golden gap, catches gross regressions only) plus tight 5% guards on the era-stable stats. Authoritative ES gate remains ninja_parity's 0.5.4b fixture goldens. Record both #11 items in docs/adapting-to-0.5.4b.md. --- crates/pobr-build/tests/parity/pob2_parity.rs | 14 +++++++- docs/adapting-to-0.5.4b.md | 32 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/pobr-build/tests/parity/pob2_parity.rs b/crates/pobr-build/tests/parity/pob2_parity.rs index a56deb7e..ad2657f9 100644 --- a/crates/pobr-build/tests/parity/pob2_parity.rs +++ b/crates/pobr-build/tests/parity/pob2_parity.rs @@ -175,7 +175,19 @@ fn deadeye_parity_report() { #[test] fn martial_parity_report() { let data = load_data(); - let (out, _pob2) = report("MARTIAL", MARTIAL, &data); + let (out, pob2) = report("MARTIAL", MARTIAL, &data); assert!(out.life.is_finite() && out.mana.is_finite()); assert!(out.dps.is_finite()); + // EnergyShield「1.12x 高估」诊断(存量 #11-2):**旧样本 golden 失真,非 PoBR + // 多算**。本 code `targetVersion="0_1"`(PoE2 0.1 时代导出),内嵌 PlayerStat + // EnergyShield=6257;同一 XML 喂当前 vendor(tools/pob2-oracle)得 7008, + // PoBR 7005.5 = 0.9996x 现行 vendor。0.1→0.5.x 间未变的口径(Life/Mana/ + // Evasion/三抗/Crit)内嵌值与现行 oracle 逐位一致,唯 ES(与进攻侧)随数据 + // 版本漂移——与上方 deadeye 的 Mageblood/res 旧样本失效同类。断言按**现行 + // vendor 语义**守护(容差 0.15 覆盖 stale-golden 缺口 1.12x,只防大幅倒退; + // 权威 ES 门禁走 ninja_parity 的 0.5.4b fixture golden)。 + assert_within(&pob2, "EnergyShield", out.energy_shield, 0.15); + assert_within(&pob2, "Evasion", out.evasion, 0.05); + assert_within(&pob2, "Mana", out.mana, 0.05); + assert_within(&pob2, "CritChance", out.crit_chance * 100.0, 0.05); } diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 50e33cc9..954572e1 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -404,6 +404,38 @@ two builds moved. Baselines: off 73→76 @5% / 75→76 @10%, dot 27→31 @5% / the 5% band). The other 7 Mageblood wearers: no movement — their legacy modelling was already complete. +**#11 — two legacy leftovers. ✅ DONE (one real root cause, one stale golden).** + +- *druid EW "curse-slot over-application" (TotalDotDPS 1.06x)*: the #7 hypothesis + (PoBR applying Elemental Weakness that vendor keeps out of the slot) was + **wrong** — `POBR_DBG_CURSE` + `POBR_DBG_ENEMYMIT` show PoBR's single curse + slot already resolves to Temporal Chains (EW excluded, IgniteEffMult 0.6 == + vendor). The real residual was the **Temporal Chains magnitude**: support + judgement only enumerated gems whose *primary* granted effect is a support, + so Blasphemy's support half (`SupportBlasphemyPlayer`, additional granted + effect carrying `support_blasphemy_curse_effect_+%_final` → `CurseEffect + MORE -41` @L19, act_int.lua:1126-1163) never entered the curse local-effect + multiplier. TC applied at floor(-25×0.55)=-13% expire-slower vs vendor + floor(-25×(1.1×0.59×0.5))=-8 (CalcPerform.lua:2422-2430 inc/more chain + + Pinnacle `CurseEffectOnSelf MORE -50`), inflating ignite + `debuffDurationMult` (CalcOffence.lua:1845) 1/0.87 vs 1/0.92. Fix: expand + `judge_group_supports` candidates to each gem's granted-effect list + (`gem_effects` overlay) routed by `is_support` — vendor CalcSetup gemList + semantics — and carry the support *effect id* to all consumers. After: + ignite duration 4.3478 == oracle, druid TotalDotDPS 1.06x→1.00x, monk + frost-bomb 1.07x→0.99x, dot baseline 31→33 @5%; per-build curse slot + occupancy unchanged across all 18 builds. +- *MARTIAL embedded-sample ES 1.12x (pob2_parity.rs)*: **stale golden, not a + PoBR over-count.** The code is a `targetVersion="0_1"` (PoE2 0.1-era) export; + feeding the same decoded XML to the current vendor oracle yields + EnergyShield 7008 vs PoBR 7005.5 (0.9996x), while every era-stable stat + (Life/Mana/Evasion/resists/crit) matches the embedded values exactly — only + ES (and offence) drifted with 0.5.x data. Same class as the deadeye + Mageblood/res stale-sample notes in that file; documented inline and + guarded with a wide (0.15) tolerance against the embedded value plus tight + guards on the era-stable stats. Authoritative ES gate remains ninja_parity's + 0.5.4b fixture goldens. + ## Tooling - `examples/demo-bd-test/tools/recapture_golden.py` — refresh fixture goldens From 57f703e5379a449d46f0bc41808d3173097bd7d3 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:30:22 +0800 Subject: [PATCH 47/74] fix(special): Point Blank distance-ramp MORE damage via curated entries The engine mis-parsed the Deadeye ascendancy notable 'Projectiles deal 20% more Hit damage to targets in the first 3.5 metres ... reach 0% after 7 metres' as a flat ProjectileDamage MORE 20 with leftover text, and the production path dropped it entirely -- ranger-deadeye-explosive-grenade lost a uniform 1.20x on every damage component (AverageDamage/TotalDPS 0.83x, oracle: MORE Damage 100 + DistanceRamp {35:0.2, 70:0} from Tree:41875, vendor ModParser.lua:2911-2913, eval'd at SkillDist=20 => +20% more). - special_mod.rs: compile DistanceRamp tags from curated entries; ramp points are transcribed as "dist mult" text pairs (TemplateScalarDef has no nested-array shape), same semantics as the statmap engine branch. - overlay-common/special_mods.json: two literal entries for the fixed ascendancy texts (scaling-down 20%->0% and scaling-up 0%->20% variants); whole-line special-channel match also kills the previous flat mis-parse. ranger-deadeye AverageDamage/TotalDPS 0.83x -> 1.00x (132833 vs 132887). --- crates/pobr-core/src/rules/special_mod.rs | 19 ++++++++ data/overlay-common/special_mods.json | 56 +++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/crates/pobr-core/src/rules/special_mod.rs b/crates/pobr-core/src/rules/special_mod.rs index 986e3097..45ad2a17 100644 --- a/crates/pobr-core/src/rules/special_mod.rs +++ b/crates/pobr-core/src/rules/special_mod.rs @@ -663,6 +663,25 @@ fn compile_tag(tag: &TemplateTagDef) -> Option { upper, }) } + "DistanceRamp" => { + // 距离插值(vendor ModStore.lua:574-590)。TemplateScalarDef 无嵌套 + // 数组形态,ramp 点列以 `"距离 倍率"` 文本对转录(如 `["35 0.2", + // "70 0"]` = vendor `{ {35,0.2}, {70,0} }`);语义与 statmap 引擎的 + // DistanceRamp 分支一致(求值期按 `cfg.skill_distance` 线性插值)。 + let TemplateScalarDef::TextList(points) = tag.fields.get("ramp")? else { + return None; + }; + let mut ramp = Vec::with_capacity(points.len()); + for point in points { + let mut parts = point.split_whitespace(); + let (Some(dist), Some(mult), None) = (parts.next(), parts.next(), parts.next()) + else { + return None; + }; + ramp.push((dist.parse().ok()?, mult.parse().ok()?)); + } + (!ramp.is_empty()).then_some(ModTag::DistanceRamp { ramp }) + } // 未映射 tag 形态:保守跳过。 _ => None, } diff --git a/data/overlay-common/special_mods.json b/data/overlay-common/special_mods.json index 8fa7eef6..887a60cd 100644 --- a/data/overlay-common/special_mods.json +++ b/data/overlay-common/special_mods.json @@ -2244,6 +2244,62 @@ "batch": "fork-a", "source_note": "ModParser.lua:3281 -- Warrior - Smith of Kitava,function(num,_,dmgType)→mod(ArmourAppliesToDamageTaken,BASE,num,{type=ItemCondition,itemSlot=Body Armour,rarityCond=NORMAL})。vendor 函数形带 (%a+) 伤害类型捕获 + ItemCondition,extract_parser_rules 抽不出(S2 批仅手转非 body-armour 的 fire/cold/lightning 三元素,漏 chaos + 漏 body-armour-grants 变体)。legacy 经 body-armour-grants 前缀剥离 + parse_armour_applies_pct(legacy.rs:393/1621)通用处理任意类型,chaos 正常,故 engine 落后 legacy。本条把 vendor ItemCondition{Body Armour,NORMAL} 映射为 pobr `Condition:NormalBodyArmourEquipped`(orchestrator calc_orchestrator.rs:373 按体甲槽 Normal 稀有度置真,与 legacy.rs:397 同口径),消费侧 taken.rs ArmourAppliesToChaosDamageTaken。修 warrior-smith-of-kitava-shield-wall ChaosMaxHit 0.84x→1.00x" }, + { + "id": "point_blank_projectiles_more_hit_damage_scaling_down", + "pattern": "projectiles deal 20% more hit damage to targets in the first 3\\.5 metres of their movement, scaling down with distance travelled to reach 0% after 7 metres", + "vendor_pattern": "projectiles deal (%d+)%% more hit damage to targets in the first ([%d%.]+) metres of their movement, scaling [du][op]w?n? with distance travelled to reach (%d+)%% after ([%d%.]+) metres", + "mods": [ + { + "name": "Damage", + "type": "MORE", + "value": 100, + "flags": [ + "Hit", + "Projectile" + ], + "tags": [ + { + "type": "DistanceRamp", + "ramp": [ + "35 0.2", + "70 0" + ] + } + ] + } + ], + "verified": false, + "batch": "fork-a", + "source_note": "ModParser.lua:2911-2913(vendor 产 Damage MORE 100 + DistanceRamp {10*首距, num/100},{10*末距, low/100};本条为 Point Blank 升华固定文本 20%/3.5m→0%/7m 的字面转写,ramp={35,0.2},{70,0}。数值/距离不同的未来变体保守不匹配。此前引擎误产平坦 ProjectileDamage MORE 20(丢距离插值)——special 通道整行优先修正)" + }, + { + "id": "point_blank_projectiles_more_hit_damage_scaling_up", + "pattern": "projectiles deal 0% more hit damage to targets in the first 3\\.5 metres of their movement, scaling up with distance travelled to reach 20% after 7 metres", + "vendor_pattern": "projectiles deal (%d+)%% more hit damage to targets in the first ([%d%.]+) metres of their movement, scaling [du][op]w?n? with distance travelled to reach (%d+)%% after ([%d%.]+) metres", + "mods": [ + { + "name": "Damage", + "type": "MORE", + "value": 100, + "flags": [ + "Hit", + "Projectile" + ], + "tags": [ + { + "type": "DistanceRamp", + "ramp": [ + "35 0", + "70 0.2" + ] + } + ] + } + ], + "verified": false, + "batch": "fork-a", + "source_note": "ModParser.lua:2911-2913(同上规则的 scaling-up 变体固定文本 0%/3.5m→20%/7m,ramp={35,0},{70,0.2})" + }, { "id": "prevent_pct_of_damage_from_deflected_hits", "pattern": "prevent \\+(\\d+(?:\\.\\d+)?)% of damage from deflected hits", From d9e174c87bc9856555e364321fc2b8ddb79320d3 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:30:34 +0800 Subject: [PATCH 48/74] feat(offence): Barrage repeats DPS MORE channel vendor CalcOffence.lua:962-976: Barrageable skills flagged with SequentialProjectiles by the Barrage buff get 'DPS MORE (1 + Sum(BarrageRepeats)) * mod(BarrageRepeatDamage)', feeding output.DpsMultiplier (:3897). Both twister builds carry a Barrage gem (repeats base 2, repeat-damage penalty -45 at q20 => 1.65) and were missing the whole channel (TotalDPS 0.98x). - pobr-data: SkillTypes::BARRAGEABLE (PoB2 SkillType.Barrageable = 70). - stat_map_engine buff domain: admit BarrageRepeats BASE / BarrageRepeatDamage MORE mod names and the SequentialProjectiles flag (payload carried by GlobalEffect/Buff entries of BarragePlayer, act_dex.lua:216-224) so the buff pass injects them into the player db. - scaled_damage::dps_end_factors: replicate the vendor gate (Barrageable + SequentialProjectiles, minus OneShotProj / NoAdditionalProjectiles / TriggeredBySnipe) and the MoreInternal per-name-bucket percent rounding (ModList.lua:143 round(modResult, 2)): 1.65% -> x1.02 exactly, oracle-verified DpsMultiplier=1.02. huntress-spirit-walker-twister TotalDPS 0.98x -> 1.00x (99563 vs 99589); monk-martial-artist-twister 0.98x -> 1.00x (205223 vs 205319). The vendor else-branch (crossbow-barrage additionalProjectiles attack-time penalty) has no fixture coverage and is left unimplemented (noted inline). --- crates/pobr-core/src/calc/scaled_damage.rs | 33 ++++++++++++++++++- crates/pobr-core/src/rules/stat_map_engine.rs | 10 +++++- crates/pobr-data/src/skill.rs | 3 ++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/crates/pobr-core/src/calc/scaled_damage.rs b/crates/pobr-core/src/calc/scaled_damage.rs index 042a3084..4ccab54b 100644 --- a/crates/pobr-core/src/calc/scaled_damage.rs +++ b/crates/pobr-core/src/calc/scaled_damage.rs @@ -193,10 +193,41 @@ pub fn dps_end_factors( let activate_twice = db .sum(ModType::Base, cfg, &[ModName::from("GrenadeActivateTwice")]) .min(100.0); + // Barrage repeats(vendor CalcOffence.lua:962-976):Barrageable 技能被 + // Barrage buff 授予 SequentialProjectiles + BarrageRepeats 时,写入 + // `DPS MORE (1 + Σ BarrageRepeats) × mod(BarrageRepeatDamage)`。vendor 把 + // 该值**原样**当 MORE 百分比(repeats=1 → MORE 2 → ×1.02,oracle 实证 + // spirit-walker-twister DpsMultiplier=1.02)——忠实复刻,不换算为倍数。 + // ponytail: vendor 同块的 else 分支(crossbow barrage 的 additionalProjectiles + // → 攻速惩罚 ReplaceMod)无 fixture 覆盖,暂不实现。 + let barrage_repeats_more = { + let barrageable = cfg.skill_types.intersects(SkillTypes::BARRAGEABLE) + && db.flag(cfg, ModName::from("SequentialProjectiles")) + && !db.flag(cfg, ModName::from("OneShotProj")) + && !db.flag(cfg, ModName::from("NoAdditionalProjectiles")) + && !db.flag(cfg, ModName::from("TriggeredBySnipe")); + let repeats = db.sum(ModType::Base, cfg, &[ModName::from("BarrageRepeats")]); + if barrageable && repeats > 0.0 { + let repeat_damage_names = [ModName::from("BarrageRepeatDamage")]; + let repeat_damage = (1.0 + db.sum(ModType::Inc, cfg, &repeat_damage_names) / 100.0) + * db.more(cfg, &repeat_damage_names); + let dps_multi = (1.0 + repeats) * repeat_damage; + // vendor MoreInternal 逐名桶百分比取整(ModList.lua:143 + // `result * round(modResult, 2)`):Barrage Repeats 的 DPS MORE 桶 + // 折算后取整到 0.01(1.65 → ×1.0165 → ×1.02,oracle 实证 + // spirit-walker DpsMultiplier=1.02)。 + // ponytail: 与 db.more("DPS") 分桶各自取整——vendor 把二者并在同一 + // "DPS" 名桶先乘后取整,语料中无并存场景,出现时再并桶。 + ((1.0 + dps_multi / 100.0) * 100.0).round() / 100.0 + } else { + 1.0 + } + }; let dps_multiplier = skill_dps_multiplier.unwrap_or(1.0) * (1.0 + db.sum(ModType::Inc, cfg, &dps_names) / 100.0) * db.more(cfg, &dps_names) - * (1.0 + activate_twice / 100.0); + * (1.0 + activate_twice / 100.0) + * barrage_repeats_more; let quantity_multiplier = db .sum(ModType::Base, cfg, &[ModName::from("QuantityMultiplier")]) .max(1.0); diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index d93aa8fb..90ff4f63 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -817,6 +817,12 @@ fn translate_player_buff_mod_name(name: &str) -> Result, Unsup // `inject_per_x_multipliers` 预灌(cfg.multipliers["Mana"] = 全管线池值)。 // monk-invoker-frost-bomb TotalDPS 0.66x 根因(缺 80% 闪电 gain-as)。 "DamageGainAsLightning" => Ok(vec!["DamageGainAsLightning"]), + // (#10-2)Barrage buff(BarragePlayer `empower_barrage_*`,act_dex.lua: + // 216-224):`BarrageRepeats` BASE / `BarrageRepeatDamage` MORE。消费方 = + // `calc::scaled_damage::dps_end_factors` 的 Barrage repeats DPS 乘区 + // (vendor CalcOffence.lua:962-976,Barrageable + SequentialProjectiles 门控)。 + "BarrageRepeats" => Ok(vec!["BarrageRepeats"]), + "BarrageRepeatDamage" => Ok(vec!["BarrageRepeatDamage"]), other => Err(UnsupportedReason::UnknownModName(other.to_string())), } } @@ -877,7 +883,9 @@ fn collect_player_buff_flag( items: &mut Vec, ) -> Result<(), UnsupportedReason> { let name = element.name.as_deref().unwrap_or("?"); - if !is_cross_type_ailment_flag(name) { + // `SequentialProjectiles`(Barrage buff,act_dex.lua:219):消费方 = + // `dps_end_factors` 的 Barrage repeats 门控(vendor CalcOffence.lua:962)。 + if !is_cross_type_ailment_flag(name) && name != "SequentialProjectiles" { return Err(UnsupportedReason::UnknownModName(format!("flag:{name}"))); } if !element.flags.is_empty() { diff --git a/crates/pobr-data/src/skill.rs b/crates/pobr-data/src/skill.rs index 6905fa39..219e8e74 100644 --- a/crates/pobr-data/src/skill.rs +++ b/crates/pobr-data/src/skill.rs @@ -76,6 +76,9 @@ impl SkillTypes { /// 「Persistent Buffs have N% less Reservation」(Tactician『A Solid /// Plan』)以 Persistent+Buff 双 tag AND 限定作用域) pub const PERSISTENT: Self = Self::bit(139); + /// PoB2 SkillType.Barrageable = 70(Barrage buff 的 repeats DPS 乘区 + /// 门控,vendor CalcOffence.lua:962-976) + pub const BARRAGEABLE: Self = Self::bit(69); /// 第 `index` 位(0-based)置 1 的掩码(const 构造)。 const fn bit(index: u32) -> Self { From 73c0834fe6156844c5a6dcdc810400e37435a944 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:30:48 +0800 Subject: [PATCH 49/74] fix(items): drop bare typed weapon adds when the weapon is not the source vendor Item.lua:1923-1928 folds every bare 'Adds N to M Damage' line on a weapon into weaponData (local: it only fires when attacking with that weapon). PoBR only treated the physical variant as local; elemental/chaos lines leaked into the global added-damage bucket and got multiplied by the skill's added-damage effectiveness even for skills that never use the weapon. warrior-titan-shield-wall: Nebuloch's 'Adds 30 to 52 Chaos damage' became a phantom 104-180 chaos base on Shield Wall (oracle ChaosMinBase 0) => TotalDPS 1.05x; warrior-smith-of-kitava: Fortified Hammer's 'Adds 3 to 55 Lightning' likewise => AverageDamage 1.02x. The overestimate was previously masked by the missing warcry gain. inject_items now drops bare typed adds (all five types) from a weapon slot whose contribution is not consumed by the main skill (non-weapon attacks like Shield Wall, spells, inactive off-hand). Active weapon sources keep the existing global-injection approximation (numerically equal to the vendor weaponData fold; deadeye/twister pinned at 1.00x, druid spell unaffected -- its spear's bare adds are attack-gated and never reached the spell). titan TotalDPS 1.05x -> 1.01x, smith AverageDamage 1.02x -> 1.00x. Remaining titan ~0.9% decomposes into the known shield-armour recompute divergence (rolled 'Armour: 1736' vs vendor recompute 1725 feeding Shield Wall's per-15-shield-armour phys base; same root as the Armour 0.99x panel row) plus crit-chance rounding (11.28 vs 11.2476) -- defence-side channel, not an offence bug. --- .../pobr-build/src/calc_orchestrator/mod.rs | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index 15e1d973..85fd958c 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -421,7 +421,16 @@ pub fn calculate_with_data_session( inject_defence_base(&mut session, build, data); // 2. 装备:归因路径注入(逐件 filter / Kalandra 镜射 / 局部词条剔除 / 槽位加成数值副本)。 - inject_items(&mut session, build, data, ctx.off_weapon.is_some())?; + let main_weapon_active = ctx + .main_effect + .is_some_and(|e| e.is_attack() && !e.is_non_weapon_attack()); + inject_items( + &mut session, + build, + data, + ctx.off_weapon.is_some(), + main_weapon_active, + )?; // 2b. 珠宝(天赋树/深渊槽):词条按全局注入。 stage_inject_jewels(&mut session, &ctx)?; @@ -1813,12 +1822,14 @@ fn inject_character_base( } /// 2 阶段:装备归因路径注入——逐件 filter / Kalandra 镜射 / 局部词条(武器·防御·Spirit) -/// 剔除 / add_item / 槽位加成效果数值副本。`off_weapon_active` = 副手武器源是否被消费。 +/// 剔除 / add_item / 槽位加成效果数值副本。`off_weapon_active` = 副手武器源是否被消费; +/// `main_weapon_active` = 主技能以 Weapon1 为伤害源(持武攻击)。 fn inject_items( session: &mut CalculationSession, build: &Build, data: &BuildData, off_weapon_active: bool, + main_weapon_active: bool, ) -> Result<(), BuildError> { // 槽位加成效果(『N% increased bonuses gained from Equipped Rings and Amulets』, // Ritualist 等):对应槽位物品词条按 scale 追加缩放副本(PoB2 CalcPerform.lua: @@ -1844,6 +1855,39 @@ fn inject_items( filtered.modifier_texts = drop_local(filtered.modifier_texts); filtered.enchant_texts = drop_local(filtered.enchant_texts); } + // 非伤害源武器的裸「Adds N to M Damage」剔除(#10-3,titan/smith + // 高估根因):vendor Item.lua:1923-1928 把武器上全类型裸 adds 折入 + // weaponData(局部,只随该武器攻击生效)。主技能不以该武器为伤害源 + // (非武器攻击如 Shield Wall / 法术 / 未消费副手)时,这些词条不得进 + // 全局加法桶(titan Nebuloch『Adds 30 to 52 Chaos damage』经 added + // effectiveness 放大 → TotalDPS 1.05x)。该武器**是**伤害源时维持现状: + // 裸元素/混沌 adds 走全局注入近似(与 vendor weaponData 折算数值等价, + // deadeye/twister 1.00x 钉住)。 + let weapon_source_inactive = (slot == EquipmentSlot::Weapon1 && !main_weapon_active) + || (slot == EquipmentSlot::Weapon2 && !off_weapon_active); + if weapon_source_inactive && data.weapon_base(&item.base.to_string()).is_some() { + const TYPED_ADDS_SUFFIXES: [&str; 5] = [ + "physical damage", + "fire damage", + "cold damage", + "lightning damage", + "chaos damage", + ]; + let drop_typed_adds = |texts: Vec| -> Vec { + texts + .into_iter() + .filter(|t| { + let clean = clean_item_text(t); + !TYPED_ADDS_SUFFIXES + .iter() + .any(|s| parse_adds_with_suffix(&clean, s).is_some()) + }) + .collect() + }; + filtered.implicit_texts = drop_typed_adds(filtered.implicit_texts); + filtered.modifier_texts = drop_typed_adds(filtered.modifier_texts); + filtered.enchant_texts = drop_typed_adds(filtered.enchant_texts); + } // 护甲件:剔除局部「increased / +flat Armour/Evasion/ES」(已折入 rolled 件级底值 / // 基底兜底乘区,见 defence_base_modifiers);留在全局会重复(且错误地变成全局加法)。 // 判定护甲件:有基底护甲项 **或** 文本给出 rolled 防御行(兜底覆盖无 catalog 的 unique)。 From 0c67f2a6aa268cdf11e40b439d2aec6eb5764923 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:30:56 +0800 Subject: [PATCH 50/74] test(parity): ratchet off/dot baselines after #10; debug aids - Baselines re-pinned to the post-#10 dashboard: offensive 76->78/80 @5% and @10% (deadeye AverageDamage+TotalDPS enter the band), dot 31->34 @5% / 34->36 @10% (deadeye ignite chain follows the Point Blank fix; smith/titan dot back in band after the local-adds leak removal). - POBR_ONLY_BUILD= filter for the ninja dashboard so POBR_DBG_* channels stay readable on a single build (never set in CI). - POBR_DBG_BASES now also dumps generic-bucket MORE contributions (POBR_POOL_MORE), symmetric with the existing INC dump. --- crates/pobr-build/tests/parity/ninja_parity.rs | 16 ++++++++++++---- crates/pobr-core/src/calc/damage.rs | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 1010ba53..ea8fd49c 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -37,6 +37,14 @@ fn discover_builds() -> Vec { .filter(|p| p.is_dir() && p.join("code.txt").exists() && p.join("meta.json").exists()) .collect(); dirs.sort(); + // Debug aid: POBR_ONLY_BUILD= narrows the dashboard to matching + // build dirs so POBR_DBG_* channels stay readable. Never set in CI. + if let Ok(filter) = std::env::var("POBR_ONLY_BUILD") { + dirs.retain(|p| { + p.file_name() + .is_some_and(|n| n.to_string_lossy().contains(&filter)) + }); + } dirs } @@ -759,8 +767,8 @@ const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refract // (CritChance/CritMult 1.00x;ES 12124→12437 vs golden 12434、MaxHit 五族 + // TotalEHP 0.97-0.98x→1.00x——def 列先前已在 5% 带内,def 计数不变)。 // 18 build 逐格 diff 仅此两 build 变动。 -const BASELINE_OFF_HIT5: usize = 76; // #8 Zarokh's Gift 后 76/80(#6+#7 合并 73;#6 单独 71;#7 单独 60;迁移基线 39) -const BASELINE_OFF_HIT10: usize = 76; // #8 后 76/80(#6+#7 合并 75;#6 单独 73;#7 单独 66;迁移基线 47;0.5.0=74) +const BASELINE_OFF_HIT5: usize = 78; // #10 进攻残差精修后 78/80(deadeye Point Blank DistanceRamp 0.83x→1.00x +2 格;twister Barrage repeats 0.98→1.00、warrior 局部 adds 泄漏 1.02/1.05→1.00 原在带内;#8 后 76;#6+#7 合并 73;迁移基线 39) +const BASELINE_OFF_HIT10: usize = 78; // #10 后 78/80(#8 后 76;#6+#7 合并 75;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -864,8 +872,8 @@ const BASELINE_OFF_HIT10: usize = 76; // #8 后 76/80(#6+#7 合并 75;#6 单 // 0.996→1.02)经点燃 ∝ 火源² 放大——命中侧存量项,非 warcry 机制。titan 随动: // TotalDotDPS 0.98→1.01、TotalDPS 0.98→1.05(同为被遮蔽的原有高估暴露, // 仍在 @5% 带内)。其余 16 build 无 warcry,逐值不变。 -const BASELINE_DOT_HIT5: usize = 31; // #8+#9 合并实测 31/37(#8 单独 31;#9 单独 27;迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 34; // #8+#9 合并实测 34/37(#8 单独 33;#9 单独 32;迁移基线 11;0.5.0=28) +const BASELINE_DOT_HIT5: usize = 34; // #10 后 34/37(deadeye ignite 链随 Point Blank 修复 0.69x→1.00x + smith/titan dot 随局部 adds 剔除回带;#8+#9 合并 31;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 36; // #10 后 36/37(#8+#9 合并 34;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 diff --git a/crates/pobr-core/src/calc/damage.rs b/crates/pobr-core/src/calc/damage.rs index 8baae0f5..7cbc0867 100644 --- a/crates/pobr-core/src/calc/damage.rs +++ b/crates/pobr-core/src/calc/damage.rs @@ -405,6 +405,12 @@ pub(crate) fn aggregate_inc_more( c.name, c.value, c.raw_text ); } + for c in db.contributions(ModType::More, cfg, &generic_names) { + eprintln!( + "[POBR_POOL_MORE] {:?} {} src={:?}", + c.name, c.value, c.raw_text + ); + } } // PoB2-PoE2:类型化 inc/more 只按分量**最终伤害类型**聚合,**不**叠加转换源类型 From 98ef99e5f9feb98c717f4c3ba87ea435523df0a4 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 19:31:58 +0800 Subject: [PATCH 51/74] docs: record #10 offence-residual closures in the 0.5.4b ledger --- docs/adapting-to-0.5.4b.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 50e33cc9..cf2fc5a8 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -307,6 +307,12 @@ Remaining offence after #6, each triaged: - `deadeye` 0.832x — the long-registered pre-0.5.4b per-hit shortfall (pob2_parity.rs); golden did move in the flip but the residual matches the old ledger. + ✅ (2026-07-17, #10) 根因钉死:Deadeye 升华 Point Blank(Tree:41875)的 + 「first 3.5m 20% more → 7m 后 0%」被引擎误析为平坦 ProjectileDamage MORE 20 + 且带 leftover 被产线丢弃——所有分量统一少 ×1.20(oracle:MORE Damage 100 + + DistanceRamp {35:0.2,70:0},SkillDist=20 求值 +20%,ModParser.lua:2911-2913)。 + 修复 = special 通道字面条目 ×2 + special 编译器 DistanceRamp tag 支持。 + AverageDamage/TotalDPS 0.83x→1.00x,连带 ignite 链 TotalDotDPS 0.69x→1.00x。 - `blood-mage` 0.880x / `abyssal-lich` 0.926x — Mageblood mod family (oracle pins blood-mage's missing `INC CritChance 107 'Mageblood'`; golden CritChance 72.45→92.1 and CritMult 5.34→5.87 moved with 0.5.4b while PoBR @@ -339,6 +345,19 @@ Remaining offence after #6, each triaged: - `spirit-walker`/`monk-twister` last ~2%: "Barrage Repeats" MORE DPS (vendor `output.DpsMultiplier` via `calcLib.mod(..., "DPS")`) — repeat DPS bonus channel unwired. + ✅ (2026-07-17, #10) 通道接通:Barrage buff 载荷(BarrageRepeats BASE 2 / + BarrageRepeatDamage MORE -45 / SequentialProjectiles flag)经 buff 域允收 + 名单入 player db,`dps_end_factors` 复刻 vendor 门控(CalcOffence.lua:962-976) + 与 MoreInternal 逐名桶百分比取整(ModList.lua:143:MORE 1.65 → ×1.02, + oracle DpsMultiplier=1.02 逐位)。两 build TotalDPS 0.98x→1.00x。 + vendor else 分支(crossbow barrage 攻时惩罚)无语料覆盖,未实现(代码内登记)。 + 同批(#10-3)smith/titan 命中高估根因 = 非伤害源武器上的裸「Adds N to M + Damage」泄漏进全局加法桶(vendor Item.lua:1923-1928 全类型折入 + weaponData 局部;titan Nebuloch 混沌 30-52 × added-effectiveness 3.47 = + 幻影 104-180 混沌 base,oracle ChaosMinBase=0)。剔除后 titan TotalDPS + 1.05x→1.01x、smith AverageDamage 1.02x→1.00x;titan 残余 ~0.9% 与 + Armour 面板 0.99x 同根(盾 rolled 1736 vs vendor 重算 1725 喂 + per-15-shield-armour phys base)+ 暴击率尾差,属防御侧通道。 Remaining elsewhere, re-triage against fresh `defenceModList` dumps: the wolf-pack EHP remainder decomposed above From fe87c985ef31c41c2f1104c53b48b0dd33a6c061 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 22:07:07 +0800 Subject: [PATCH 52/74] fix(minion): scale summons off the ally life table at effective gem level Vendor uses monsterAllyLifeTable for non-hostile minions (CalcPerform.lua:1046, floor semantics), not the enemy monsterLifeTable PoBR was reading. Promote the ally table to a Rust const (MONSTER_ALLY_LIFE_TABLE, deduping the literal previously inlined in the catalog Default impl) and switch derive_minion_base_stats to it. Oracle pin (wolf-pack): ally[44]=2938 x minionData.life 1.1 = 3231 base life, matching the vendor Life BASE mod exactly. --- crates/pobr-core/src/calc/minion.rs | 6 ++++- .../pobr-data/src/catalog/monster_scaling.rs | 25 ++++++------------- crates/pobr-data/src/monster.rs | 24 ++++++++++++++++++ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/crates/pobr-core/src/calc/minion.rs b/crates/pobr-core/src/calc/minion.rs index 7b77969b..237f35b4 100644 --- a/crates/pobr-core/src/calc/minion.rs +++ b/crates/pobr-core/src/calc/minion.rs @@ -266,7 +266,11 @@ pub fn derive_minion_base_stats(gem_level: u32, data: &MinionData) -> MinionBase let level = minion_level_from_gem_level(gem_level); let row = MonsterScalingRow::at_level(level); - let life = round(row.life as f64 * data.life); + // 生命走**盟友表**(vendor CalcPerform.lua:1046 `m_floor(monsterAllyLifeTable + // [level] × minionData.life)`——PoBR 召唤物全为玩家盟友;敌对召唤物 + // (hostile spectre,用 monsterLifeTable × mapLevelLifeMult)未建模)。 + // wolf-pack 钉值:ally[44]=2938 × 1.1 = 3231(oracle Life BASE 3231)。 + let life = (pobr_data::monster::monster_ally_life(level) as f64 * data.life).floor(); let armour = round(row.armour as f64 * data.armour); let evasion = round(row.evasion as f64 * data.evasion); // 生命转 ES:energy_shield 占比 × 100 = LifeConvertToEnergyShield BASE,这里直接折算 ES 基础。 diff --git a/crates/pobr-data/src/catalog/monster_scaling.rs b/crates/pobr-data/src/catalog/monster_scaling.rs index 4f749ef2..6a65bf96 100644 --- a/crates/pobr-data/src/catalog/monster_scaling.rs +++ b/crates/pobr-data/src/catalog/monster_scaling.rs @@ -134,10 +134,12 @@ impl MonsterScalingTable { /// fallback(无 GameData 注入时使用):与 `base/monster_scaling.json` **逐值相等** /// (搬迁不变式,W2 测试已锁 JSON == Rust 准源)。 /// -/// - 有 pobr Rust 准源的七张表直接引用 `crate::monster` 既有 const(零字面量复制); -/// - `ally_life` / `ally_damage` 为 vendor-only(pobr 此前未迁、calc 暂无消费方), -/// 字面量抄录自 `vendor/PathOfBuilding-PoE2/src/Data/Misc.lua` L8 / L10 -/// (与 `base/monster_scaling.json` 同源同值;`ally_damage` 为 2 位小数口径)。 +/// - 有 pobr Rust 准源的八张表(含 #12 升格的 `ally_life` = +/// `MONSTER_ALLY_LIFE_TABLE`,消费方 = 召唤物基础生命派生)直接引用 +/// `crate::monster` 既有 const(零字面量复制); +/// - `ally_damage` 为 vendor-only(calc 暂无消费方),字面量抄录自 +/// `vendor/PathOfBuilding-PoE2/src/Data/Misc.lua` L10 +/// (与 `base/monster_scaling.json` 同源同值;2 位小数口径)。 impl Default for MonsterScalingTable { fn default() -> Self { Self { @@ -145,20 +147,7 @@ impl Default for MonsterScalingTable { evasion: MONSTER_EVASION_TABLE.to_vec(), armour: MONSTER_ARMOUR_TABLE.to_vec(), life: MONSTER_LIFE_TABLE.to_vec(), - ally_life: vec![ - 51, 83, 116, 150, 186, 223, 261, 300, 341, 382, // lv1-10 - 426, 471, 517, 565, 614, 665, 718, 772, 828, 886, // lv11-20 - 945, 1007, 1070, 1135, 1203, 1272, 1344, 1417, 1493, 1571, // lv21-30 - 1652, 1734, 1820, 1907, 1998, 2091, 2186, 2285, 2386, 2490, // lv31-40 - 2598, 2708, 2821, 2938, 3058, 3181, 3307, 3438, 3571, 3709, // lv41-50 - 3850, 3995, 4144, 4298, 4455, 4617, 4783, 4953, 5128, 5308, // lv51-60 - 5493, 5682, 5877, 6077, 6282, 6492, 6708, 6930, 7157, 7391, // lv61-70 - 7630, 7876, 8128, 8387, 8652, 8924, 9203, 9489, 9783, 10084, // lv71-80 - 10393, 10710, 11034, 11367, 11708, 12058, 12417, 12785, 13161, - 13548, // lv81-90 - 13944, 14350, 14766, 15192, 15629, 16076, 16535, 17005, 17486, - 17980, // lv91-100 - ], + ally_life: crate::monster::MONSTER_ALLY_LIFE_TABLE.to_vec(), damage: MONSTER_DAMAGE_TABLE.to_vec(), ally_damage: vec![ 3.11, 4.42, 5.82, 7.31, 8.92, 10.63, 12.46, 14.42, // lv1-8 diff --git a/crates/pobr-data/src/monster.rs b/crates/pobr-data/src/monster.rs index d3174708..af567cd1 100644 --- a/crates/pobr-data/src/monster.rs +++ b/crates/pobr-data/src/monster.rs @@ -251,6 +251,25 @@ pub const MONSTER_LIFE_TABLE: [u32; MONSTER_TABLE_LEN] = [ 43001, 44291, 45619, 46988, 48398, 49850, 51345, 52885, 54472, 56106, // lv91-100 ]; +/// 盟友(非敌对召唤物)生命值查表(等级 1..=100)。 +/// +/// 来源:PoB2 `src/Data/Misc.lua::data.monsterAllyLifeTable`(L8)。召唤物基础生命 +/// = `floor(monsterAllyLifeTable[level] × minionData.life)`(CalcPerform.lua:1046; +/// 敌对召唤物才用 `monsterLifeTable` × mapLevelLifeMult)。与敌方表不同:无 lv65+ +/// 终局跳升,平滑单调。 +pub const MONSTER_ALLY_LIFE_TABLE: [u32; MONSTER_TABLE_LEN] = [ + 51, 83, 116, 150, 186, 223, 261, 300, 341, 382, // lv1-10 + 426, 471, 517, 565, 614, 665, 718, 772, 828, 886, // lv11-20 + 945, 1007, 1070, 1135, 1203, 1272, 1344, 1417, 1493, 1571, // lv21-30 + 1652, 1734, 1820, 1907, 1998, 2091, 2186, 2285, 2386, 2490, // lv31-40 + 2598, 2708, 2821, 2938, 3058, 3181, 3307, 3438, 3571, 3709, // lv41-50 + 3850, 3995, 4144, 4298, 4455, 4617, 4783, 4953, 5128, 5308, // lv51-60 + 5493, 5682, 5877, 6077, 6282, 6492, 6708, 6930, 7157, 7391, // lv61-70 + 7630, 7876, 8128, 8387, 8652, 8924, 9203, 9489, 9783, 10084, // lv71-80 + 10393, 10710, 11034, 11367, 11708, 12058, 12417, 12785, 13161, 13548, // lv81-90 + 13944, 14350, 14766, 15192, 15629, 16076, 16535, 17005, 17486, 17980, // lv91-100 +]; + /// 怪物基础伤害查表(等级 1..=100,f64)。 /// /// 来源:PoB2 `src/Data/Misc.lua::data.monsterDamageTable`(DefaultMonsterStats.dat)。 @@ -376,6 +395,11 @@ pub fn monster_life(level: u32) -> u32 { MONSTER_LIFE_TABLE[level_to_index(level)] } +/// 查询盟友(非敌对召唤物)生命值(等级 1..=100,超界自动 clamp)。 +pub fn monster_ally_life(level: u32) -> u32 { + MONSTER_ALLY_LIFE_TABLE[level_to_index(level)] +} + /// 查询怪物基础伤害(等级 1..=100,超界自动 clamp)。 pub fn monster_damage(level: u32) -> f64 { MONSTER_DAMAGE_TABLE[level_to_index(level)] From 20db1e2db92cfb23e12ebe9b89b615df69e94e48 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 22:07:20 +0800 Subject: [PATCH 53/74] feat(statmap): minion-domain mapper + companion mitigation buff allow-list Two narrow statmap channels for the companion defence layer (#12): - map_minion_life_stat: translate 'MinionModifier LIST { mod = Life ... }' statmap payloads (e.g. Loyalty's support_trusty_companion_minion_life_+%_final -> Life MORE -30) into minion-side inner modifiers, normalising vendor 'Life' to the engine life-pool name 'MaximumLife'. First batch admits inner Life only (BASE/INC/MORE); anything else is skipped, never mis-applied. - admit TakenFromCompanionBeforeYou through the player-buff allow-list (Loyalty / Wild Protector carry companion_takes_%_damage_before_you_from_support with GlobalEffect/Buff/unscalable, SkillStatMap.lua:2559-2561). --- crates/pobr-core/src/rules/stat_map_engine.rs | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/crates/pobr-core/src/rules/stat_map_engine.rs b/crates/pobr-core/src/rules/stat_map_engine.rs index 90ff4f63..e79d7e2c 100644 --- a/crates/pobr-core/src/rules/stat_map_engine.rs +++ b/crates/pobr-core/src/rules/stat_map_engine.rs @@ -823,6 +823,12 @@ fn translate_player_buff_mod_name(name: &str) -> Result, Unsup // (vendor CalcOffence.lua:962-976,Barrageable + SequentialProjectiles 门控)。 "BarrageRepeats" => Ok(vec!["BarrageRepeats"]), "BarrageRepeatDamage" => Ok(vec!["BarrageRepeatDamage"]), + // (#12 companion allies 层)Loyalty support(SupportLoyaltyPlayer)的 + // `companion_takes_%_damage_before_you_from_support` → BASE 10(GlobalEffect + // /Buff/unscalable,SkillStatMap.lua:2559-2561)。消费方 = perform 的 + // `inject_companion_life` 门控 + `pool_setup::build_pool_state` companion + // 先扣层(CalcDefence.lua:2961-2965 / :3656-3663)。 + "TakenFromCompanionBeforeYou" => Ok(vec!["TakenFromCompanionBeforeYou"]), other => Err(UnsupportedReason::UnknownModName(other.to_string())), } } @@ -1008,6 +1014,92 @@ fn collect_player_buff_mod( Ok(()) } +// ---- #12:minion 域(MinionModifier LIST 载荷 → 召唤物内层 mod) ---- + +/// 把一条 support/skill stat 经 statmap 翻译为**召唤物侧**内层 modifier 列表 +/// (`MinionModifierEntry.inner` 载荷;消费方 = 编排层 spawn_minions → +/// `build_minion_context` 通道 1)。 +/// +/// vendor 语义:statmap 的 `mod("MinionModifier","LIST",{ mod = mod() })` +/// 随 support 并入被支援技能 skillModList(CalcActiveSkill.lua merge 循环), +/// `addMinionModifiers`(CalcPerform.lua:1668-1686)再把内层 mod 注入**该技能的** +/// 召唤物 modDB——作用域是组内被支援技能的 minion,不是全局。典型 = Loyalty +/// support `support_trusty_companion_minion_life_+%_final` → Life MORE −30 +/// (wolf-pack 伴侣生命 3231 × 0.7 = 2262 的 MORE 因子,oracle 钉值)。 +/// +/// 第一批允收内层 = `Life`(BASE/INC/MORE;vendor 名 `Life` 归一到 PoBR 生命池 +/// 聚合名 `MaximumLife`,与 buff 域 [`translate_player_buff_mod_name`] 同口径)。 +/// 其余内层(伤害/速度族)暂不准入——minion DPS 不在 parity 面板,宁可跳过不可 +/// 错算。保守门控:外层带 flags/keyword_flags/tags/scalar、内层含 +/// kind/mod_type/name 之外的键的条目整条跳过(返回空,不上报——窄通道, +/// 未准入形态无消费方)。 +pub fn map_minion_life_stat( + catalog: &StatMapCatalog, + effect_id: &str, + set_key: Option<&str>, + stat: &str, + stat_value: f64, +) -> Vec { + let Some(entry) = catalog.lookup(effect_id, set_key, stat) else { + return Vec::new(); + }; + if entry.unextractable { + return Vec::new(); + } + let params = MergeParams { + div: entry.div, + mult: entry.mult, + base: entry.base, + value: entry.value, + }; + let mut out = Vec::new(); + for element in &entry.mods { + if element.kind != "mod" + || element.name.as_deref() != Some("MinionModifier") + || element.mod_type.as_deref() != Some("LIST") + || !element.flags.is_empty() + || !element.keyword_flags.is_empty() + || !element.tags.is_empty() + || element.scalar.is_some() + { + continue; + } + let Some(StatMapValue::Table(wrapper)) = &element.value else { + continue; + }; + let Some(StatMapValue::Table(inner)) = wrapper.get("mod") else { + continue; + }; + // 内层只接受 `{ kind:"mod", mod_type, name:"Life" }` 的裸形态 + // (带 tags/flags 的内层 = 额外门控语义,跳过)。 + if wrapper.len() != 1 + || inner + .keys() + .any(|k| !matches!(k.as_str(), "kind" | "mod_type" | "name")) + { + continue; + } + if !matches!(inner.get("name"), Some(StatMapValue::Text(n)) if n == "Life") { + continue; + } + let mod_type = match inner.get("mod_type") { + Some(StatMapValue::Text(t)) => match t.as_str() { + "BASE" => ModType::Base, + "INC" => ModType::Inc, + "MORE" => ModType::More, + _ => continue, + }, + _ => continue, + }; + out.push(Modifier::number( + "MaximumLife", + mod_type, + params.merge(stat_value), + )); + } + out +} + // ---- M4-L:debuff 域(GlobalEffect effectType=Debuff)敌侧映射 ---- /// 元素是否为**敌侧 debuff 载荷**(vendor `GlobalEffect` tag 且 @@ -3329,6 +3421,51 @@ mod tests { ); } + /// (#12)Loyalty 形态(per_stat_set["SupportLoyaltyPlayer"]["1"]): + /// `MinionModifier LIST { mod = Life MORE }` → 召唤物内层 `MaximumLife MORE` + /// (名归一,与 buff 域 Life→MaximumLife 同口径)。allow-list 外内层 + /// (Damage)与带 tag 的外层整条跳过(返回空)。 + #[test] + fn minion_life_stat_maps_loyalty_more_life() { + let catalog = catalog_json( + r#"{ "global": {}, "per_stat_set": { "SupportLoyaltyPlayer": { "1": { + "support_trusty_companion_minion_life_+%_final": { + "mods": [ { "kind": "mod", "name": "MinionModifier", "mod_type": "LIST", + "value": { "mod": { "kind": "mod", "mod_type": "MORE", + "name": "Life" } } } ] }, + "minion_damage_+%": { + "mods": [ { "kind": "mod", "name": "MinionModifier", "mod_type": "LIST", + "value": { "mod": { "kind": "mod", "mod_type": "INC", + "name": "Damage" } } } ] } } } } }"#, + ); + let mods = map_minion_life_stat( + &catalog, + "SupportLoyaltyPlayer", + None, + "support_trusty_companion_minion_life_+%_final", + -30.0, + ); + assert_eq!(mods.len(), 1); + assert_eq!(mods[0].name.as_str(), "MaximumLife"); + assert_eq!(mods[0].mod_type, ModType::More); + assert_eq!(mods[0].value.as_number(), Some(-30.0)); + // 内层非 Life(Damage):窄通道未准入 → 空。 + assert!( + map_minion_life_stat( + &catalog, + "SupportLoyaltyPlayer", + None, + "minion_damage_+%", + 20.0 + ) + .is_empty() + ); + // 未知 stat → 空。 + assert!( + map_minion_life_stat(&catalog, "SupportLoyaltyPlayer", None, "no_such", 1.0).is_empty() + ); + } + /// Precision II 形态(sup_dex.lua:4216-4250):`Accuracy INC` + GlobalEffect /// Buff(含 effectName 键,无门控语义)→ 玩家侧 Accuracy INC,tag 剥净。 #[test] From 20bfcd5a21b2f0c71779e6429e4c2685a100b53f Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 17 Jul 2026 22:07:44 +0800 Subject: [PATCH 54/74] feat(companion): ally-mitigation defence layer into EHP/max-hit (#12) Implements the vendor companion allies layer (CalcDefence.lua:2961-2965 mitigation, :493-495/:525-531 EHP drain, :3656-3663 max-hit poolProtected fold; CalcPerform.lua:3323-3370 life pool): - Actor::is_companion marks damageable-companion minions at spawn (granting skill has SkillType.Companion and not MinionsAreUndamagable); add_minion_from_def/session wrapper take the flag. - spawn_minions now feeds summons the effective gem level (+N to Level of ... bonuses + support-granted levels, vendor minionLevelTable[activeEffect.level]) and appends group-scoped support minion payloads via map_minion_life_stat (Loyalty -30% more life). - perform runs perform_minions before fill_mechanics (vendor computes minion life pools before player defence) and inject_companion_life writes TotalCompanionLife BASE into the player ModDb (Override-aware, vendor CalcPerform.lua:3364-3370). - build_pool_state emits the 'companion' AllyLayer consumed by the existing shared EHP/max-hit allies machinery. The deflected-hits term (TakenFromCompanionBeforeYouFromDeflected x DeflectChance) is not modelled: no source in the 18-build corpus and the wolf-pack oracle pins mitigation at exactly 10 (pure hits term). Design note: no companion actor abstraction - the existing minion actor plus the generic AllyLayer machinery express the whole mechanic; the only new state is one bool on Actor and one BASE mod on the player db. Oracle pins (mercenary-tactician-wolf-pack): TotalCompanionLife 2262 = floor(ally[44] 2938 x 1.1) x 0.7; CompanionAllyDamageMitigation 10; TotalHitPool 3826.67 (PoBR now 3817.0 = 0.9975, remainder is the pre-existing Mana 761.2-vs-770 gap). Parity: huntress-spirit-walker-twister closes exactly (5x MaxHit + TotalEHP 0.89-0.90x -> 1.00x); wolf-pack MaxHit 0.82->0.90x, ChaosMaxHit 0.72->0.80x, TotalEHP 0.74->0.83x (residual is a uniform per-type taken-multiplier gap, not the companion layer). Baselines ratcheted: def 425->431 @5%, 434->439 @10%; only these two builds moved in the 18-build per-cell diff. --- crates/pobr-build/examples/dump_components.rs | 6 ++ .../src/calc_orchestrator/skill_resolve.rs | 70 ++++++++++++++-- .../pobr-build/tests/parity/ninja_parity.rs | 14 +++- crates/pobr-core/src/calc/actor.rs | 5 ++ crates/pobr-core/src/calc/env.rs | 10 ++- crates/pobr-core/src/calc/perform.rs | 48 ++++++++++- crates/pobr-core/src/calc/pool_setup.rs | 83 ++++++++++++++++++- crates/pobr-core/src/calc/session.rs | 2 + crates/pobr-core/tests/engine/perform_fill.rs | 11 ++- docs/adapting-to-0.5.4b.md | 62 ++++++++++++++ 10 files changed, 294 insertions(+), 17 deletions(-) diff --git a/crates/pobr-build/examples/dump_components.rs b/crates/pobr-build/examples/dump_components.rs index 90dd4852..a7f763d7 100644 --- a/crates/pobr-build/examples/dump_components.rs +++ b/crates/pobr-build/examples/dump_components.rs @@ -56,6 +56,12 @@ fn main() { out.mana, out.mana_unreserved, ); + for (i, m) in out.minions.iter().enumerate() { + println!( + " minion[{i}]: level={} life={:.2} armour={:.2} evasion={:.2} es={:.2}", + m.level, m.life, m.armour, m.evasion, m.energy_shield, + ); + } println!( " maxhit: phys={:.1} fire={:.1} cold={:.1} light={:.1} chaos={:.1} total_ehp={:.1}", out.physical_max_hit_pob2, diff --git a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs index be89c126..f6c356e3 100644 --- a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs +++ b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs @@ -85,6 +85,65 @@ pub(crate) fn spawn_minions( if minion_ids.is_empty() { continue; } + // 召唤物等级取**生效宝石等级**(vendor `data.minionLevelTable[activeEffect + // .level]`,CalcActiveSkill.lua:948——activeEffect.level 含 applyGemMods + // 的 `+N to Level of all Skills` 与 support 授予等级)。wolf-pack + // 「+4 to Level of all Minion Skills」:gem 18 → 22 → 怪物等级 44 + // (oracle 钉值;修正前 36 → 生命 1013 vs 2262)。 + let effective_gem_level = gem_level + .saturating_add(additional_gem_levels(build, data, skill_id)) + .saturating_add(support_granted_gem_levels(build, data, skill_id)); + // (#12 companion)伴侣判定:授予技能 `SkillType.Companion` 且非 + // `MinionsAreUndamagable`(vendor CalcPerform.lua:3365-3367 的 + // includeSkill 谓词)→ 该技能的召唤物计入 `TotalCompanionLife`。 + let is_companion = data.granted_effects.get(skill_id).is_some_and(|e| { + e.skill_types.iter().any(|t| t == "Companion") + && !e.skill_types.iter().any(|t| t == "MinionsAreUndamagable") + }); + // (#12)同组兼容 support 的召唤物侧载荷(vendor:support statmap 的 + // `MinionModifier LIST` 并入被支援技能 skillModList → + // `addMinionModifiers` 注入**该技能的** minion modDB,组内作用域)。 + // 数据通道 = `map_minion_life_stat`(第一批只收内层 Life,如 Loyalty + // 的 −30% more minion life)。 + let mut group_minion_modifiers = minion_modifiers.clone(); + if let Some(catalog) = data.stat_map_catalog.as_deref() { + use pobr_core::calc::minion::MinionModifierEntry; + use pobr_core::rules::stat_map_engine::map_minion_life_stat; + for sup in super::triggers::judge_group_supports(group, data, skill_id).compatible { + let sup_gem = &group.gem_skills[sup.gem_index]; + let set_index = (sup_gem.skill_id == sup.effect_id) + .then_some(sup_gem.stat_set_index) + .flatten(); + // quality 传 0 与 support_modifiers 同口径(support 品质表条目不存在)。 + let stats = data.effect_stats(&sup.effect_id, sup_gem.gem_level, 0, set_index); + let set_key = data.selected_set_key(&sup.effect_id, set_index); + for ds in stats.all() { + if ds.value == 0.0 { + continue; + } + for inner in map_minion_life_stat( + catalog, + &sup.effect_id, + set_key.as_deref(), + &ds.stat, + ds.value, + ) { + let origin = ModifierSource::new(SourceId::new( + SourceKind::SupportGem, + format!("minion.{}.{}", sup.effect_id, ds.stat), + )) + .with_raw_text(format!( + "minion {} {} ({})", + sup.effect_id, ds.stat, ds.value + )); + group_minion_modifiers.push(MinionModifierEntry { + inner: inner.with_origin(origin), + minion_type: None, + }); + } + } + } + } for minion_id in minion_ids { if !seen.insert(minion_id.clone()) { continue; @@ -105,16 +164,17 @@ pub(crate) fn spawn_minions( if summed >= 1.0 { summed as u32 } else { 1 } }; let def = def.clone(); - // `add_minion_from_def` 内部经 `minion_level_from_gem_level` 把 gem_level - // 映射到怪物等级(CalcActiveSkill.lua:896 默认规则),故此处传 gem_level - // 原值,不可预先 resolve(否则二次映射)。 + // `add_minion_from_def` 内部经 `minion_level_from_gem_level` 把宝石等级 + // 映射到怪物等级(CalcActiveSkill.lua:948 默认规则),故此处传生效 + // 宝石等级(含 +N to Level 加成),不可预先 resolve(否则二次映射)。 session.add_minion_from_def( &def, - gem_level, + effective_gem_level, limit, - minion_modifiers.clone(), + group_minion_modifiers.clone(), Vec::new(), AttributeInfusion::default(), + is_companion, ); } } diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 09c53d8a..dc1fd8f4 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -646,8 +646,18 @@ const BASELINE_DEF_CORE_HIT5: usize = 142; // 存量 #7-3/4(charm-guard 摘除 // **0.5.4b #4 Communion/LowLife + Voices 重记(+3 @5% 410→413 / +5 @10% 429→434)**: // core-8 的 SpiritUnres/LifeUnres 翻正(见 BASELINE_DEF_CORE_HIT5 上说明)+ // abyssal-lich EnergyShield 0.93x→0.98x(Voices sinister 珠宝的 ES 词条找回)。 -const BASELINE_DEF_HIT5: usize = 425; // 存量 #7-3/4 后 425/450(ritualist 6 格 + gemling 9 格 + wolf-pack Life/Armour 精确翻正,wolf-pack MaxHit 族随 charm-guard 掩蔽摘除移出——余量 = companion ally-mitigation 层;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) -const BASELINE_DEF_HIT10: usize = 434; // Communion+Voices 后 434/450(Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) +// **#12 companion allies 层重记(+6 @5% 425→431 / +5 @10% 434→439)**:伴侣先扣层 +// 落地(TakenFromCompanionBeforeYou buff 允收 + TotalCompanionLife 求和注入 + +// pool_setup companion AllyLayer;连带 = 召唤物等级吃 `+N to Level of Minion +// Skills`、召唤物基础生命改走 monsterAllyLifeTable、Loyalty 的 −30% more minion +// life 经 minion 域 statmap 通道注入)。spirit-walker-twister 6 格全翻正 +// (5×MaxHit + TotalEHP 0.89-0.90x→1.00x 精确闭合,Bear Companion + Wild +// Protector 自带 10% taken);wolf-pack MaxHit 族 0.82x→0.90x / ChaosMaxHit +// 0.72x→0.80x / TotalEHP 0.74x→0.83x(池侧 3817 vs oracle 3826.67 = 0.9975 已 +// 闭合,余量 = per-type taken 乘子侧 ~10% 均匀缺口 + Mana 761.2 vs 770,均与 +// companion 层无关)。 +const BASELINE_DEF_HIT5: usize = 431; // #12 companion 后 431/450(存量 #7-3/4 425:ritualist 6 格 + gemling 9 格 + wolf-pack Life/Armour 精确翻正,wolf-pack MaxHit 族随 charm-guard 掩蔽摘除移出;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +const BASELINE_DEF_HIT10: usize = 439; // #12 companion 后 439/450(Communion+Voices 434;Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 // ReservationPlayer、buff 侧 BannerPlayer(Aura)在附加位)在 buff_skill_specs diff --git a/crates/pobr-core/src/calc/actor.rs b/crates/pobr-core/src/calc/actor.rs index 39a71416..88cb4054 100644 --- a/crates/pobr-core/src/calc/actor.rs +++ b/crates/pobr-core/src/calc/actor.rs @@ -44,6 +44,10 @@ pub struct Actor { pub base: ActorBaseStats, pub output: OutputTable, pub breakdown: BreakdownTable, + /// 可受伤伴侣召唤物(授予技能 `SkillType.Companion` 且非 + /// `MinionsAreUndamagable`,spawn 侧判定)。`TotalCompanionLife` 求和 + /// (vendor CalcPerform.lua:3364-3370)只计此类召唤物;玩家 Actor 恒 false。 + pub is_companion: bool, } impl Actor { @@ -54,6 +58,7 @@ impl Actor { base, output: OutputTable::default(), breakdown: BreakdownTable::default(), + is_companion: false, } } } diff --git a/crates/pobr-core/src/calc/env.rs b/crates/pobr-core/src/calc/env.rs index 784c87ef..b35527f4 100644 --- a/crates/pobr-core/src/calc/env.rs +++ b/crates/pobr-core/src/calc/env.rs @@ -114,6 +114,11 @@ impl Env { /// /// `limit` 通常由玩家技能 `skillModList:Sum(limitName)` 派生(本阶段由调用方给定)。 /// `limit == 0` 时仍写入(multiplier=0,等价无召唤数量贡献,向后兼容)。 + /// + /// `is_companion`:授予技能是 `SkillType.Companion` 且非 + /// `MinionsAreUndamagable`(调用方按 skill_types 判定)——`TotalCompanionLife` + /// 求和(vendor CalcPerform.lua:3364-3370)只计此类召唤物。 + #[allow(clippy::too_many_arguments)] pub fn add_minion_from_def( &mut self, def: &super::MinionDef, @@ -122,6 +127,7 @@ impl Env { minion_modifiers: Vec, ally_buff_mods: Vec, infusion: super::AttributeInfusion, + is_companion: bool, ) -> &mut Self { let ctx = super::build_minion_context_from_def( def, @@ -131,7 +137,9 @@ impl Env { infusion, ); super::write_summoned_minion_multipliers(&mut self.player.mod_db, limit, &def.id); - self.minions.push(minion_actor_from_context(&ctx)); + let mut actor = minion_actor_from_context(&ctx); + actor.is_companion = is_companion; + self.minions.push(actor); self } diff --git a/crates/pobr-core/src/calc/perform.rs b/crates/pobr-core/src/calc/perform.rs index 17883205..b13a1416 100644 --- a/crates/pobr-core/src/calc/perform.rs +++ b/crates/pobr-core/src/calc/perform.rs @@ -170,6 +170,14 @@ pub fn perform(env: &mut Env) -> Result<(), CalcError> { env.player.breakdown = BreakdownTable::from_steps(output.breakdown); calc_defence(&mut env.player, &env.cfg, env.enemy.base.accuracy); + // 召唤物(Lane4):每个召唤物是独立 Actor,复用玩家同款 offence/defence 管线。 + // 无召唤物时该段空转。位置在 fill_mechanics **之前**(vendor 先例: + // CalcPerform.lua:3323-3370 calcMinionLifePool 在 calcs.defence(env.player) + // 之前算好召唤物生命)——伴侣总生命(inject_companion_life)要先于 EHP/max-hit + // 池整备写入玩家 ModDb。 + perform_minions(env); + inject_companion_life(env); + fill_mechanics(env); // 弩 reload 折算(M4-T4 W-D2):紧跟 fill_mechanics——vendor 顺序先服务器帧 // cap(calc_skill_use_time 内)后 reload(CalcOffence.lua:2864-2867);下游 @@ -184,13 +192,45 @@ pub fn perform(env: &mut Env) -> Result<(), CalcError> { // 只读消费异常侧 bleed/poison/ignite 现值(ailment.rs 不改,T4 一波约定)。 fill_skill_dot_stage(env); - // 召唤物(Lane4):每个召唤物是独立 Actor,复用玩家同款 offence/defence 管线。 - // 无召唤物时该段空转,行为与无此字段时完全一致(向后兼容)。 - perform_minions(env); - Ok(()) } +/// (#12 companion allies 层)伴侣总生命入库(vendor CalcPerform.lua:3364-3370): +/// 玩家有 `TakenFromCompanionBeforeYou`(Loyalty support 的 +/// `companion_takes_%_damage_before_you_from_support` buff 载荷)且无 +/// `TotalCompanionLife` Override(config 覆盖通道)时,把全部**可受伤伴侣**召唤物 +/// (`Actor::is_companion`,spawn 侧按授予技能 SkillType 判定)的生命求和写入玩家 +/// `TotalCompanionLife` BASE。消费方 = `pool_setup::build_pool_state` 的 companion +/// 先扣层(EHP reduce_pools 与 max-hit extend_total_hit_pool 共用)。 +fn inject_companion_life(env: &mut Env) { + let taken_name = [ModName::from("TakenFromCompanionBeforeYou")]; + if env.player.mod_db.sum(ModType::Base, &env.cfg, &taken_name) == 0.0 { + return; + } + if env + .player + .mod_db + .override_(&env.cfg, ModName::from("TotalCompanionLife")) + .is_some() + { + return; + } + let total: f64 = env + .minions + .iter() + .filter(|m| m.is_companion) + .map(|m| m.output.life) + .sum(); + env.player.mod_db.add_mod( + crate::Modifier::number("TotalCompanionLife", ModType::Base, total).with_origin( + ModifierSource::new(SourceId::new( + SourceKind::GameConstant, + "companion.total_life", + )), + ), + ); +} + /// 对每个召唤物跑同一套 offence/defence 管线,并把关键输出快照收集到玩家 /// `OutputTable.minions`。召唤物复用 `calculate_minimal_vs_enemy` + `calc_defence`, /// 不另写公式。召唤物对敌人的命中沿用玩家敌人配置(同一 `env.enemy`)。 diff --git a/crates/pobr-core/src/calc/pool_setup.rs b/crates/pobr-core/src/calc/pool_setup.rs index 2daed384..dfb91580 100644 --- a/crates/pobr-core/src/calc/pool_setup.rs +++ b/crates/pobr-core/src/calc/pool_setup.rs @@ -17,7 +17,8 @@ //! `pool_damage.rs` 状态机只消费固化后的值类型;本文件同样不写 `Env`。 use crate::calc::pool_damage::{ - PoolCtx, PoolState, apply_protected_layer, life_hit_pool_with_loss_prevention, pool_protected, + AllyLayer, PoolCtx, PoolState, apply_protected_layer, life_hit_pool_with_loss_prevention, + pool_protected, }; use crate::rules::DefenceKeystones; use crate::{CalcConfig, ModDb}; @@ -114,8 +115,9 @@ pub fn build_pool_ctx( /// 构造扣池前的池快照(CalcDefence.lua:2821-2881 + reducePoolsByDamage 入口 /// :490-516 的 output 读出口径)。 /// -/// allies 段本阶段为空 Vec(玩家无 frost shield / soul link 来源时 vendor 同样为空, -/// 结构保留给后续 minion/party 接入)。 +/// allies 段目前只接 companion 层(`TakenFromCompanionBeforeYou` + +/// `TotalCompanionLife`,#12);frost shield / spectre / totem / soul link 等 +/// 其余先扣层无来源时 vendor 同样为空,结构保留给后续接入。 pub fn build_pool_state(db: &ModDb, cfg: &CalcConfig, base: &PoolBaseStats) -> PoolState { // Aegis(:2860-2877):modDB:Max 取最强单源(非求和)。 let aegis_shared = db.max_of(ModType::Base, cfg, &[ModName::from("AegisValue")]); @@ -156,8 +158,34 @@ pub fn build_pool_state(db: &ModDb, cfg: &CalcConfig, base: &PoolBaseStats) -> P 0.0 }; } + // 伴侣先扣层(#12;CalcDefence.lua:2961-2965 整备 + :493-495/:3087-3088 EHP + // 表 + :3656-3663 max-hit):`TakenFromCompanionBeforeYou` ≠ 0 时取 + // `TotalCompanionLife`(config Override 优先,否则 perform 注入的 BASE 和)。 + // deflected 项(`TakenFromCompanionBeforeYouFromDeflected × DeflectChance`, + // :2962-2963)未建模——18-build 语料无来源(wolf-pack oracle 钉值 mitigation + // 恰为 10 = 纯 hits 项),接入时需在此叠加。 + let mut allies = Vec::new(); + let companion_rate = db.sum( + ModType::Base, + cfg, + &[ModName::from("TakenFromCompanionBeforeYou")], + ); + if companion_rate != 0.0 { + let companion_life_name = ModName::from("TotalCompanionLife"); + let companion_life = db + .override_(cfg, companion_life_name.clone()) + .unwrap_or_else(|| db.sum(ModType::Base, cfg, &[companion_life_name])); + if companion_life > 0.0 { + allies.push(AllyLayer { + id: "companion", + remaining: companion_life, + mitigation_pct: companion_rate, + damage_type: None, + }); + } + } PoolState { - allies: Vec::new(), + allies, aegis_shared, aegis_shared_elemental, aegis_by_type, @@ -492,6 +520,53 @@ mod tests { assert_eq!(state.energy_shield, 600.0); } + /// (#12)companion 先扣层整备(:2961-2965):mitigation ≠ 0 且生命 > 0 时 + /// 产出 allies 层;Override 优先于 BASE 求和(config `TotalCompanionLife` + /// 覆盖通道);mitigation = 0 或生命 = 0 → allies 空。 + #[test] + fn build_pool_state_companion_ally_layer() { + let cfg = CalcConfig::new(); + // 无 mitigation → 空。 + assert!( + build_pool_state(&ModDb::new(), &cfg, &base_stats()) + .allies + .is_empty() + ); + // wolf-pack 钉值形态:10% + 2262(oracle TotalCompanionLife)。 + let mut db = ModDb::new(); + db.add_list([ + Modifier::number("TakenFromCompanionBeforeYou", ModType::Base, 10.0), + Modifier::number("TotalCompanionLife", ModType::Base, 2262.0), + ]); + let state = build_pool_state(&db, &cfg, &base_stats()); + assert_eq!(state.allies.len(), 1); + assert_eq!(state.allies[0].id, "companion"); + assert_eq!(state.allies[0].remaining, 2262.0); + assert_eq!(state.allies[0].mitigation_pct, 10.0); + assert_eq!(state.allies[0].damage_type, None); + // Override 优先。 + let mut db2 = ModDb::new(); + db2.add_list([ + Modifier::number("TakenFromCompanionBeforeYou", ModType::Base, 10.0), + Modifier::number("TotalCompanionLife", ModType::Base, 2262.0), + Modifier::number("TotalCompanionLife", ModType::Override, 500.0), + ]); + let state2 = build_pool_state(&db2, &cfg, &base_stats()); + assert_eq!(state2.allies[0].remaining, 500.0); + // mitigation 有、生命 0 → 空(vendor :3656 `TotalCompanionLife > 0` 门控)。 + let mut db3 = ModDb::new(); + db3.add_list([Modifier::number( + "TakenFromCompanionBeforeYou", + ModType::Base, + 10.0, + )]); + assert!( + build_pool_state(&db3, &cfg, &base_stats()) + .allies + .is_empty() + ); + } + /// MinimumBypass = 全类型 min(:2709-2721)。 #[test] fn minimum_es_bypass_takes_min() { diff --git a/crates/pobr-core/src/calc/session.rs b/crates/pobr-core/src/calc/session.rs index c0ab8dfb..a1a1fb10 100644 --- a/crates/pobr-core/src/calc/session.rs +++ b/crates/pobr-core/src/calc/session.rs @@ -367,6 +367,7 @@ impl CalculationSession { minion_modifiers: Vec, ally_buff_mods: Vec, infusion: super::AttributeInfusion, + is_companion: bool, ) { self.env.add_minion_from_def( def, @@ -375,6 +376,7 @@ impl CalculationSession { minion_modifiers, ally_buff_mods, infusion, + is_companion, ); } diff --git a/crates/pobr-core/tests/engine/perform_fill.rs b/crates/pobr-core/tests/engine/perform_fill.rs index dcad7e4c..a7427670 100644 --- a/crates/pobr-core/tests/engine/perform_fill.rs +++ b/crates/pobr-core/tests/engine/perform_fill.rs @@ -1287,7 +1287,15 @@ fn perform_minion_from_def_with_limit() { env.cfg = CalcConfig::attack().with_damage_type(DamageType::Physical); let def = minion_def_zombie(); - env.add_minion_from_def(&def, 20, 3, vec![], vec![], AttributeInfusion::default()); + env.add_minion_from_def( + &def, + 20, + 3, + vec![], + vec![], + AttributeInfusion::default(), + false, + ); perform(&mut env).unwrap(); // 数量上限写入玩家 multiplier。 @@ -1334,6 +1342,7 @@ fn perform_minion_damage_per_summoned_minion_uses_limit() { vec![entry], vec![], AttributeInfusion::default(), + false, ); perform(&mut env).unwrap(); env.player.output.minions[0].dps diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 59dbe8ea..5de25544 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -455,6 +455,68 @@ modelling was already complete. guards on the era-stable stats. Authoritative ES gate remains ninja_parity's 0.5.4b fixture goldens. +**#12 — companion allies defence layer. ✅ DONE (exact on twister; wolf-pack +residual is mitigation-side, not companion).** Vendor semantics (all pinned by +oracle on `mercenary-tactician-wolf-pack`): + +- *Mitigation* (`CalcDefence.lua:2961-2965`): `CompanionAllyDamageMitigation = + Σ BASE TakenFromCompanionBeforeYou + Σ BASE + TakenFromCompanionBeforeYouFromDeflected × DeflectChance/100`. The 10% comes + from Loyalty support's constant stat + `companion_takes_%_damage_before_you_from_support` (SkillStatMap.lua:2559 → + `TakenFromCompanionBeforeYou` BASE, GlobalEffect/Buff/unscalable); Wild + Protector (twister's bear) carries the same stat on its own stat set. +- *Pool* (`CalcPerform.lua:3323-3370` `calcMinionLifePool`): when the player + has the mitigation mod and no config Override, `TotalCompanionLife` = Σ life + of minions whose granting skill has `SkillType.Companion` and not + `MinionsAreUndamagable`. Oracle decomposition for the wolf: minion level 44 + (= `minionLevelTable[18+4]` — gem 18 + item "+4 to Level of all Minion + Skills"), base life `floor(monsterAllyLifeTable[44]=2938 × minionData.life + 1.1) = 3231`, × Loyalty `support_trusty_companion_minion_life_+%_final` + MORE −30 → **2262** (`TotalCompanionLife` golden). +- *Consumption*: max hit (`:3656-3663`) folds the layer with the shared + poolProtected formula (`protected = L/(r)×(1−r)`; pool < protected ⇒ + pool/(1−r), i.e. ×1/0.9); EHP (`:493-495` + `reducePoolsByDamage:525-531`) + drains `damage × r` capped by remaining companion life, not recoupable. + +PoBR implementation (minimal slice — no companion actor abstraction; reuses +the existing minion actor + the existing generic `AllyLayer` machinery that +EHP/max-hit already shared): + +- `TakenFromCompanionBeforeYou` admitted through the player-buff statmap + allow-list (`stat_map_engine::translate_player_buff_mod_name`) — reaches the + player ModDb via the existing support/active buff channels (Loyalty via + `support_buff_specs`, Wild Protector via the active-skill Buff branch). +- Minion fixes feeding the life sum (all vendor-faithful, benefit every + summoner): spawn level now includes `+N to Level` gem bonuses + (`spawn_minions` → `additional_gem_levels` + `support_granted_gem_levels`); + minion base life switched from the enemy `monsterLifeTable` to + `monsterAllyLifeTable` (new `MONSTER_ALLY_LIFE_TABLE` const, vendor + CalcPerform.lua:1046 floor semantics); Loyalty's −30% more minion life flows + through a new narrow minion-domain statmap mapper + (`map_minion_life_stat`, inner allow-list = Life → `MaximumLife`) into the + group's `MinionModifierEntry` channel. +- `Actor::is_companion` marks companion minions at spawn (skill_types + judgement); `perform` now runs `perform_minions` *before* `fill_mechanics` + (vendor precedent: minion life pools are computed before player defence) and + `inject_companion_life` writes `TotalCompanionLife` BASE into the player + ModDb (Override-aware); `pool_setup::build_pool_state` emits the + `companion` AllyLayer consumed by both EHP and max-hit paths. +- Not modelled: the `...FromDeflected × DeflectChance` term (no source in the + 18-build corpus; wolf-pack oracle mitigation is exactly 10 = pure hits + term) and multi-companion stacking (no corpus; the sum loop is already + general over `env.minions`). + +Results: PoBR wolf pool 3817.0 vs oracle `TotalHitPool` 3826.67 +(0.9975 — remaining 0.25% is the pre-existing Mana 761.2 vs 770 gap); +`huntress-spirit-walker-twister` closes exactly (5×MaxHit + TotalEHP +0.89-0.90x → 1.00x); wolf-pack moves MaxHit 0.82→0.90x, ChaosMaxHit +0.72→0.80x, TotalEHP 0.74→0.83x — the residual is a uniform ~10% per-type +taken-multiplier gap (armour-applies/mitigation side, tracked separately), +not the companion layer. Baselines: def 425→431 @5%, 434→439 @10%; off/dot/ +core-8 unchanged; 18-build per-cell diff shows only twister and wolf-pack +moving. + ## Tooling - `examples/demo-bd-test/tools/recapture_golden.py` — refresh fixture goldens From 699e9e30f7da4eaa0a255a80e6983422a789b23f Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:04:42 +0800 Subject: [PATCH 55/74] fix(defence): round armour DR to integer pct in taken-hit chain (vendor parity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendor's TakenHit chain (CalcDefence.lua:2402) uses calcs.armourReduction — the *integer-rounding* variant (Common.lua round = floor(x+0.5)) — while only takenHitFromDamage (:437) uses the fractional armourReductionF. PoBR's taken_hit_per_type used the fractional value everywhere, so the exported PhysicalDamageReduction never matched PoB2's always-integer golden at small magnitudes. druid-oracle-ember-fusillade PhysDR 4.23 -> 4.00 (1.06x -> 1.00x) and ranger-deadeye-explosive-grenade PhysDR 2.39 -> 2.00 (1.20x -> 1.00x). Def 25-col 431 -> 433 @5%, 439 -> 440 @10%. --- crates/pobr-core/src/calc/ehp.rs | 7 +++++-- crates/pobr-core/tests/defence/ehp_pob2.rs | 13 +++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/pobr-core/src/calc/ehp.rs b/crates/pobr-core/src/calc/ehp.rs index e5dc6f5e..f55ddc09 100644 --- a/crates/pobr-core/src/calc/ehp.rs +++ b/crates/pobr-core/src/calc/ehp.rs @@ -577,8 +577,11 @@ pub fn taken_hit_per_type( let mut dr_pct = [0.0_f64; 5]; for i in 0..5 { let damage = taken[i]; - // :2379 armourReduct = min(drMax, armourReductionF(effArmour, damage))。 - let armour_dr = (armour_reduction(mit.effective_applied_armour[i], damage) * 100.0) + // :2402 armourReduct = min(drMax, armourReduction(effArmour, damage))—— + // 注意这是**取整**变体(Common.lua round=floor(x+0.5);armourReductionF 才是 + // 小数变体,仅 takenHitFromDamage/:437 用)。golden PhysicalDamageReduction + // 恒为整数即由此来。 + let armour_dr = vendor_round(armour_reduction(mit.effective_applied_armour[i], damage) * 100.0) .min(mit.dr_max_pct[i]); // :2382 totalReduct = min(drMax, armourReduct + flatDR)。 let total_dr = (armour_dr + mit.flat_dr_pct[i]).min(mit.dr_max_pct[i]); diff --git a/crates/pobr-core/tests/defence/ehp_pob2.rs b/crates/pobr-core/tests/defence/ehp_pob2.rs index 6a65f2a1..bd6093c9 100644 --- a/crates/pobr-core/tests/defence/ehp_pob2.rs +++ b/crates/pobr-core/tests/defence/ehp_pob2.rs @@ -296,9 +296,10 @@ fn taken_hit_per_type_neutral_identity() { assert_eq!(dr, [0.0; 5]); } -/// 抗性 + 护甲减伤(:2379-2383/:2442):fire 抗 75%、物理 effArmour 2000 vs 进伤 1000 -/// → 护甲 DR = 2000/(2000+10×1000) = 16.67%(armour_ratio=10,四舍到 9 位)→ -/// 物理承受 = 1000 × (1−0.1667) = 833.33…;fire 承受 = 500 × 0.25 = 125。 +/// 抗性 + 护甲减伤(:2402-2409/:2442):fire 抗 75%、物理 effArmour 2000 vs 进伤 1000 +/// → 护甲 DR = round(2000/(2000+10×1000)×100) = round(16.67) = **17%**(vendor +/// `calcs.armourReduction` 是取整变体,Common.lua round=floor(x+0.5))→ +/// 物理承受 = 1000 × (1−0.17) = 830;fire 承受 = 500 × 0.25 = 125。 #[test] fn taken_hit_per_type_applies_resist_and_armour() { // Arrange @@ -315,9 +316,9 @@ fn taken_hit_per_type_applies_resist_and_armour() { let (taken, dr) = taken_hit_per_type(&damage, &mit); // Assert - let armour_dr = 2000.0 / (2000.0 + 10.0 * 1000.0); // 0.166666… - assert!((dr[0] - armour_dr * 100.0).abs() < 1e-6); - assert!((taken.physical - 1000.0 * (1.0 - armour_dr)).abs() < 1e-3); + let armour_dr_pct = (2000.0_f64 / (2000.0 + 10.0 * 1000.0) * 100.0).round(); // 17 + assert!((dr[0] - armour_dr_pct).abs() < 1e-6); + assert!((taken.physical - 1000.0 * (1.0 - armour_dr_pct / 100.0)).abs() < 1e-3); assert!((taken.fire - 125.0).abs() < 1e-9); } From 8b742d80fb70d10adec3478e215a3252a107e448 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:12:48 +0800 Subject: [PATCH 56/74] feat(pools): apply Life/Mana ConvertTo-defence deduction to pool values Vendor doActorLifeManaSpirit (CalcDefence.lua:92-95) deducts min(Sum(BASE, res..ConvertTo{EnergyShield,Armour,Evasion}), 100) percent from the Life/Mana pool base. PoBR's defence matrix already credited the receiving side (ES gains ceil(base*rate/100), :1364) but the pool-side deduction was explicitly deferred and never landed, so builds with 'N% of Maximum Life Converted to Energy Shield' kept full Life. witch-abyssal-lich-detonate-dead allocates 0_5-tree 'Enhanced Barrier' (node 44299, 5% Life -> ES; oracle extraModList pins LifeConvertToEnergyShield BASE 5 from Tree:44299): Life 1782.98 -> 1693.83 vs golden 1694 (1.05x -> 1.00x), LifeUnreserved follows. Def 25-col 433 -> 435 @5%, core-8 142 -> 143. --- crates/pobr-core/src/calc/offence.rs | 45 ++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/crates/pobr-core/src/calc/offence.rs b/crates/pobr-core/src/calc/offence.rs index 172f9731..46a92c82 100644 --- a/crates/pobr-core/src/calc/offence.rs +++ b/crates/pobr-core/src/calc/offence.rs @@ -1345,7 +1345,46 @@ fn apply_server_tick_cap(db: &ModDb, cfg: &CalcConfig, rate: f64) -> f64 { pub(crate) fn scaled_pool(db: &ModDb, cfg: &CalcConfig, base: f64, name: &str) -> f64 { let names = [ModName::from(name)]; - scaled_numeric_stat(db, cfg, base, &names) + let conv = pool_conversion_pct(db, cfg, name); + if conv == 0.0 { + return scaled_numeric_stat(db, cfg, base, &names); + } + // vendor CalcDefence.lua:92-95:`(base × (1 − conv/100) + extra) × (1+inc) × more`。 + // OVERRIDE 仍然胜过一切(ChaosInoculation 等池钳定)。 + for n in &names { + if let Some(value) = db.override_(cfg, n.clone()) { + return round(value); + } + } + let base_value = base + db.sum(ModType::Base, cfg, &names); + let inc = db.sum(ModType::Inc, cfg, &names); + let more = db.more(cfg, &names); + round(base_value * (1.0 - conv / 100.0) * (1.0 + inc / 100.0) * more) +} + +/// Life/Mana 池的「N% of Maximum X Converted to 」扣减率 +/// (vendor CalcDefence.lua:92 `conv = m_min(Sum(BASE, res.."ConvertTo…"), 100)`)。 +/// 只扣池本体;ES/Armour/Evasion 侧的**转入**由 defence 矩阵按未扣减的全局底 +/// 处理(:1364 `ceil(globalBase × rate/100)`,见 calc_defence_resources)。 +// ponytail: vendor 把 conv 只作用于 base 段、Extra 免扣——PoBR 的矩阵转入 +// 现注入为 Maximum BASE,会一并被扣;fixture 集无「双向转换」build,出现时 +// 再把注入名迁到 Extra 通道。 +fn pool_conversion_pct(db: &ModDb, cfg: &CalcConfig, name: &str) -> f64 { + let prefix = match name { + "MaximumLife" => "Life", + "MaximumMana" => "Mana", + _ => return 0.0, + }; + db.sum( + ModType::Base, + cfg, + &[ + ModName::from(format!("{prefix}ConvertToEnergyShield")), + ModName::from(format!("{prefix}ConvertToArmour")), + ModName::from(format!("{prefix}ConvertToEvasion")), + ], + ) + .min(100.0) } fn scaled_numeric_stat(db: &ModDb, cfg: &CalcConfig, base: f64, names: &[ModName]) -> f64 { @@ -1403,7 +1442,9 @@ fn scaled_pool_traced( trace, format!("{stat_name} BASE modifier sum"), ); - let base_total = base + base_mods.value; + // Life/Mana 池转换扣减(vendor :92——与 scaled_pool 非追踪路径同式)。 + let conv_factor = 1.0 - pool_conversion_pct(db, cfg, stat_name) / 100.0; + let base_total = (base + base_mods.value) * conv_factor; let base_total_node = trace.add_node( format!("{stat_name} base total"), base_total, From fff5b2ea2390c0adaf8b98e0a264a321475e031b Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:16:37 +0800 Subject: [PATCH 57/74] fix(reservation): fold Blasphemy per-curse flat into base before single round 0.5.4b vendor (CalcDefence.lua:229-239) adds blasphemy_base_spirit_reservation_per_socketed_curse x instances into pool.Spirit.baseFlat *before* the one efficiency-scaled round; PoBR still used the old per-instance round-then-multiply (round(60/1.1)=55 x3 = 165 vs vendor round(180/1.1) = 164, oracle spiritReservedBreakdown pins 164). sorceress-chronomancer-essence-drain SpiritUnreserved 6 -> 7 (exact). Def 25-col 435 -> 436 @5%, 441 @10%. Skills-suite pin updated 110 -> 109. --- .../pobr-build/src/calc_orchestrator/buffs.rs | 59 ++++++++----------- .../tests/skills/spirit_reservation.rs | 11 ++-- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index 56ca5f6b..8cc194ad 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -609,17 +609,37 @@ pub(crate) fn spirit_reservation_modifiers( .map(|s| s.value / 100.0) .sum::(); } - // Blasphemy per-curse 预留(vendor CalcDefence.lua:273-284):`IsBlasphemy` - // 效果按**被包 curse 数**各加 `blasphemy_base_spirit_reservation_per_socketed_curse` - // (constant stat = 60;vendor `supportEffect.isSupporting` 计数 ≙ 同组 - // AppliesCurse 主动技能数)。被包 curse 自身预留 0(levels 无 flat, - // 进 vendor 预留循环但 baseFlat=0——两侧一致,无需额外排除)。 let es = data.effect_stats( &gem.skill_id, gem.gem_level, gem.quality, gem.stat_set_index, ); + // Blasphemy per-curse 预留(vendor CalcDefence.lua:229-239):`IsBlasphemy` + // 效果按**被包 curse 数**各加 `blasphemy_base_spirit_reservation_per_socketed_curse` + // (constant stat = 60;vendor `supportEffect.isSupporting` 计数 ≙ 同组 + // AppliesCurse 主动技能数)。0.5.4b vendor 口径 = **先并入 baseFlat 再统一 + // 缩放 round 一次**(:236-238 `values.baseFlat += flat × instances`, + // essence-drain round(180/1.1)=164 ≠ 旧的单份 round(60/1.1)=55×3=165, + // oracle spiritReservedBreakdown 钉值 164)。被包 curse 自身预留 0 + // (levels 无 flat,vendor 同——无需额外排除)。 + if has("IsBlasphemy") { + let per_curse: f64 = es + .all() + .filter(|s| s.stat == "blasphemy_base_spirit_reservation_per_socketed_curse") + .map(|s| s.value) + .sum(); + let curse_count = group + .gem_skills + .iter() + .filter(|g| { + data.granted_effects.get(&g.skill_id).is_some_and(|e| { + !e.is_support && e.skill_types.iter().any(|t| t == "AppliesCurse") + }) + }) + .count(); + flat += per_curse * curse_count as f64; + } // 预留效率(vendor :240-243/:251 `/(1 + efficiency/100)`,clamp ≥ −100): // - 宝石自身品质 stat `base_reservation_efficiency_+%`(q20 Blasphemy=10%); // - 树/装备词条族(`Spirit`/裸 `ReservationEfficiency` INC,域限定经 @@ -726,36 +746,9 @@ pub(crate) fn spirit_reservation_modifiers( } continue; } - let mut reserved = (flat * mult * res_factor / (1.0 + efficiency / 100.0) / eff_more) + let reserved = (flat * mult * res_factor / (1.0 + efficiency / 100.0) / eff_more) .round() .max(0.0); - // Blasphemy per-curse 预留(vendor CalcDefence.lua:273-284):`IsBlasphemy` - // 效果按**被包 curse 数**各加 `blasphemy_base_spirit_reservation_per_socketed_curse` - // (constant stat = 60;vendor `supportEffect.isSupporting` 计数 ≙ 同组 - // AppliesCurse 主动技能数)。口径 = **单份缩放后 round 再 ×instances** - // (:282-283 `blasphemyEffectiveFlat = round(...);reservedFlat += ... × - // instances`,essence-drain 60→55×3=165 ≠ round(180/1.1)=164)。被包 - // curse 自身预留 0(levels 无 flat,vendor 同——无需额外排除)。 - if has("IsBlasphemy") { - let per_curse: f64 = es - .all() - .filter(|s| s.stat == "blasphemy_base_spirit_reservation_per_socketed_curse") - .map(|s| s.value) - .sum(); - let curse_count = group - .gem_skills - .iter() - .filter(|g| { - data.granted_effects.get(&g.skill_id).is_some_and(|e| { - !e.is_support && e.skill_types.iter().any(|t| t == "AppliesCurse") - }) - }) - .count(); - reserved += (per_curse * mult * res_factor / (1.0 + efficiency / 100.0) / eff_more) - .round() - .max(0.0) - * curse_count as f64; - } if reserved <= 0.0 { continue; } diff --git a/crates/pobr-build/tests/skills/spirit_reservation.rs b/crates/pobr-build/tests/skills/spirit_reservation.rs index f4ee6165..08f3fb67 100644 --- a/crates/pobr-build/tests/skills/spirit_reservation.rs +++ b/crates/pobr-build/tests/skills/spirit_reservation.rs @@ -175,21 +175,22 @@ fn dedupes_across_groups_and_skips_disabled() { assert_eq!(calc(&build, &data), 0.0, "禁用组不预留"); } -/// Blasphemy per-curse 预留(vendor CalcDefence.lua:273-284):`IsBlasphemy` 效果按 +/// Blasphemy per-curse 预留(vendor CalcDefence.lua:229-239):`IsBlasphemy` 效果按 /// 同组 AppliesCurse 主动技能数各加 `blasphemy_base_spirit_reservation_per_socketed_curse` -/// (constant stat 60),口径 = 单份缩放 round 后 ×count。品质效率(:251, -/// q20 Blasphemy = 20×0.5 = 10%)除在每份上:round(60/1.1) = 55。 +/// (constant stat 60)**先并入 baseFlat**,再统一缩放 round 一次(:236-238)。 +/// 品质效率(q20 Blasphemy = 20×0.5 = 10%)除在总量上:round(120/1.1) = 109 +/// (≠ 旧的单份 round(60/1.1)=55×2=110;essence-drain oracle 钉值同口径 164)。 #[test] fn blasphemy_reserves_per_socketed_curse_with_quality_efficiency() { let data = repo_data(); - // q20 Blasphemy + 2 条 curse → 55 × 2 = 110(curse 自身预留 0)。 + // q20 Blasphemy + 2 条 curse → round(120/1.1) = 109(curse 自身预留 0)。 let build = build_with_group( SocketGroup::new() .with_gem_skill_quality("BlasphemyPlayer", 19, 20) .with_gem_skill("TemporalChainsPlayer", 19) .with_gem_skill("EnfeeblePlayer", 19), ); - assert_eq!(calc(&build, &data), 110.0); + assert_eq!(calc(&build, &data), 109.0); // q0 Blasphemy + 1 条 curse → 60(无效率缩放)。 let build = build_with_group( From 289680769ddca261a29aa42ffc32384fd4634b5b Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:28:40 +0800 Subject: [PATCH 58/74] feat(quality): altQualityStats channel gated on GemlingQuality flag Vendor gems carry a second quality-stat table (altQualityStats) that only applies when the build has 'Gem Quality grants Socketed Skills an additional effect' (Gemling ascendancy notable -> GemlingQuality flag, ModParser.lua:3353 / CalcSetup.lua:835 / CalcTools.lua:147-152). The extract-lua gem-quality channel never transcribed those rows, so gemling lost e.g. Mirage Archer's alt base_reservation_efficiency_+% x2 and Eternal Rage's alt base_spirit_reservation_efficiency_+% x0.75. - extractor: emit altQualityStats rows with alt:true; QualityStat gains a serde-default alt flag (old data unchanged); regenerated data/4.5.4.3/overlay/gem_quality_stats.json. - BuildData::effect_stats/unselected_set_stats exclude alt rows (byte-identical behavior for every existing caller); new alt_quality_stats accessor returns trunc(rate x quality) alt values. - spirit reservation consumer: detect the flag from allocated/anointed nodes (gemling_quality_flag) and fold alt efficiency stats in; also admit the spirit-specific base_spirit_reservation_efficiency_+% id. mercenary-gemling-legionnaire SpiritUnreserved -60 -> -18 (exact; oracle pins Mirage Archer 60->37 eff 62%, Eternal Rage 100->81 eff 23% at effective quality 31). Def 25-col 436 -> 437 @5%, 441 -> 442 @10%. --- crates/pobr-build/src/build_data.rs | 32 +- .../pobr-build/src/calc_orchestrator/buffs.rs | 24 +- .../pobr-build/src/calc_orchestrator/mod.rs | 1 + .../src/calc_orchestrator/skill_resolve.rs | 24 + crates/pobr-build/tests/skills/gem_quality.rs | 2 + crates/pobr-data/src/catalog/skills.rs | 10 + data/4.5.4.3/overlay/gem_quality_stats.json | 1344 ++++++++++++++++- .../src/extract_gem_quality.lua | 33 +- tools/sync-pob-catalog/src/extract_quality.rs | 4 + .../tests/extract/extract_quality.rs | 1 + 10 files changed, 1458 insertions(+), 17 deletions(-) diff --git a/crates/pobr-build/src/build_data.rs b/crates/pobr-build/src/build_data.rs index f40e2222..80625abc 100644 --- a/crates/pobr-build/src/build_data.rs +++ b/crates/pobr-build/src/build_data.rs @@ -752,6 +752,9 @@ impl BuildData { .get(skill_id) .map(|rows| { rows.iter() + // alt 品质 stat 仅 GemlingQuality build 生效,走 + // [`Self::alt_quality_stats`] 由消费方按 flag 叠加。 + .filter(|q| !q.alt) .map(|q| SkillDamageStat { stat: q.stat.clone(), // trunc(toward zero),对齐 math.modf 整数部分。 @@ -770,6 +773,32 @@ impl BuildData { } } + /// alt 品质 stat(vendor `altQualityStats`)在给定品质下的 trunc 值。 + /// + /// 仅当 build 带 GemlingQuality flag(Gemling 升华『Gem Quality grants + /// Socketed Skills an additional effect』)时叠加进技能 stat 集 + /// (PoB2 `CalcTools.lua:147-152` `includeAltQualityStats`)——由消费方 + /// 判定 flag 后调用([`Self::effect_stats`] 恒不含 alt 行)。 + // ponytail: 目前只有 spirit 预留效率消费此通道(gemling parity 钉值); + // offence/statmap 侧待有 fixture 逼出偏差时再接(同一 accessor,零改数据)。 + pub fn alt_quality_stats(&self, skill_id: &str, quality: u32) -> Vec { + if quality == 0 { + return Vec::new(); + } + self.gem_quality_stats + .get(skill_id) + .map(|rows| { + rows.iter() + .filter(|q| q.alt) + .map(|q| SkillDamageStat { + stat: q.stat.clone(), + value: (q.per_quality_rate * f64::from(quality)).trunc(), + }) + .collect() + }) + .unwrap_or_default() + } + /// 取某授予效果在某 (宝石等级, 品质, statSet 形态) 下**未选 set** 的 stat 快照 /// (W-J global-only merge 的取数源,PoB2 `CalcActiveSkill.lua:124-140`: /// 选中 set 之外的每个 statSet 以 `onlyGlobals=true` 参与 merge)。 @@ -816,7 +845,7 @@ impl BuildData { if quality > 0 && let Some(rows) = self.gem_quality_stats.get(skill_id) { - for q in rows { + for q in rows.iter().filter(|q| !q.alt) { // trunc(toward zero),与 effect_stats 的品质段同语义。 *acc.entry(q.stat.clone()).or_default() += (q.per_quality_rate * f64::from(quality)).trunc(); @@ -1018,6 +1047,7 @@ mod tests { vec![QualityStat { stat: "beta".into(), per_quality_rate: 0.55, + alt: false, }], ); diff --git a/crates/pobr-build/src/calc_orchestrator/buffs.rs b/crates/pobr-build/src/calc_orchestrator/buffs.rs index 8cc194ad..e1045527 100644 --- a/crates/pobr-build/src/calc_orchestrator/buffs.rs +++ b/crates/pobr-build/src/calc_orchestrator/buffs.rs @@ -559,6 +559,11 @@ pub(crate) fn spirit_reservation_modifiers( &pobr_core::CalcConfig::new(), pobr_data::prelude::ModName::from("AncestralBond"), ); + // GemlingQuality(升华『Gem Quality grants Socketed Skills an additional + // effect』):激活时宝石 altQualityStats 品质 stat 生效(CalcTools.lua:147-152), + // 预留效率经此获得(如 Mirage Archer alt `base_reservation_efficiency_+%` ×2、 + // Eternal Rage alt `base_spirit_reservation_efficiency_+%` ×0.75)。 + let use_alt_quality = super::skill_resolve::gemling_quality_flag(build, data); for group in build.enabled_socket_groups() { for gem in &group.gem_skills { let Some(effect) = data.granted_effects.get(&gem.skill_id) else { @@ -641,14 +646,29 @@ pub(crate) fn spirit_reservation_modifiers( flat += per_curse * curse_count as f64; } // 预留效率(vendor :240-243/:251 `/(1 + efficiency/100)`,clamp ≥ −100): - // - 宝石自身品质 stat `base_reservation_efficiency_+%`(q20 Blasphemy=10%); + // - 宝石自身品质 stat `base_reservation_efficiency_+%` / + // `base_spirit_reservation_efficiency_+%`(q20 Blasphemy=10%;后者是 + // Spirit 池限定形,statmap → SpiritReservationEfficiency,Spirit 预留 + // 两名同入 `/(1+eff/100)`); + // - GemlingQuality build 另叠 altQualityStats 同名 stat(Mirage Archer + // ×2 / Eternal Rage ×0.75,oracle gemling 62/23 钉值); // - 树/装备词条族(`Spirit`/裸 `ReservationEfficiency` INC,域限定经 // `ModTag::SkillTypes` 匹配——per-gem cfg 带该效果的类型位,vendor // skillCfg Sum 同口径。「Meta Skills have N% increased Reservation // Efficiency」(tree 42245/63236)对 Blasphemy/Archmage 等 Meta 效果生效)。 + const EFFICIENCY_STATS: [&str; 2] = [ + "base_reservation_efficiency_+%", + "base_spirit_reservation_efficiency_+%", + ]; + let alt_quality = if use_alt_quality { + data.alt_quality_stats(&gem.skill_id, gem.quality) + } else { + Vec::new() + }; let quality_eff: f64 = es .all() - .filter(|s| s.stat == "base_reservation_efficiency_+%") + .chain(alt_quality.iter()) + .filter(|s| EFFICIENCY_STATS.contains(&s.stat.as_str())) .map(|s| s.value) .sum(); let gem_cfg = pobr_core::CalcConfig::new() diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index 85fd958c..76cd17cd 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -2860,6 +2860,7 @@ mod tests { vec![QualityStat { stat: "damage_+%".into(), per_quality_rate: 0.55, + alt: false, }], ); // 直接调取数点(不经 calculate_with_data):手动安装 Data 通道上下文 diff --git a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs index f6c356e3..b25dc41c 100644 --- a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs +++ b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs @@ -502,6 +502,30 @@ pub(crate) fn gem_property_bonuses(build: &Build, data: &BuildData) -> Vec bool { + const FLAG_TEXT: &str = "gem quality grants socketed skills an additional effect"; + let matches = |stat: &str| { + clean_grant_text(stat) + .trim() + .eq_ignore_ascii_case(FLAG_TEXT) + }; + for node_id in &build.tree.allocated_nodes { + if let Some(node) = data.passive_nodes.get(&node_id.0) + && node.stats.iter().any(|s| matches(s)) + { + return true; + } + } + granted_passive_defs(build, data) + .iter() + .any(|def| def.stats.iter().any(|s| matches(s))) +} + /// 某 GemProperty 词条是否适用于指定授予效果的宝石(vendor `applyGemMods` /// keyword/keywordList 逐项 `gemIsType` + `gemRequirements` 检查, /// CalcSetup.lua:410-435)。 diff --git a/crates/pobr-build/tests/skills/gem_quality.rs b/crates/pobr-build/tests/skills/gem_quality.rs index 664e1c6b..4a69c5df 100644 --- a/crates/pobr-build/tests/skills/gem_quality.rs +++ b/crates/pobr-build/tests/skills/gem_quality.rs @@ -63,10 +63,12 @@ fn quality_segment_truncates_toward_zero() { QualityStat { stat: "synth_pos".into(), per_quality_rate: 0.55, + alt: false, }, QualityStat { stat: "synth_neg".into(), per_quality_rate: -0.55, + alt: false, }, ], ); diff --git a/crates/pobr-data/src/catalog/skills.rs b/crates/pobr-data/src/catalog/skills.rs index 689d47fb..15f9d595 100644 --- a/crates/pobr-data/src/catalog/skills.rs +++ b/crates/pobr-data/src/catalog/skills.rs @@ -388,6 +388,16 @@ pub struct QualityStat { /// stat 集——**截断取整**(toward zero),对齐 PoB2 `CalcTools.lua:142` /// `math.modf(stat[2] * skillInstance.quality)`。 pub per_quality_rate: f64, + /// alt 品质 stat(vendor `altQualityStats`):仅当 build 带 GemlingQuality + /// flag(Gemling 升华『Gem Quality grants Socketed Skills an additional + /// effect』)时叠加(PoB2 `CalcTools.lua:147-152` `includeAltQualityStats`)。 + #[serde(default, skip_serializing_if = "is_false")] + pub alt: bool, +} + +/// serde 跳过 false 布尔(diff 友好;旧数据无 `alt` 键 = false)。 +fn is_false(v: &bool) -> bool { + !*v } /// 某授予效果的品质 stat 表(`overlay/gem_quality_stats.json` 的单条)。 diff --git a/data/4.5.4.3/overlay/gem_quality_stats.json b/data/4.5.4.3/overlay/gem_quality_stats.json index 05e8b7fb..d3149aa4 100644 --- a/data/4.5.4.3/overlay/gem_quality_stats.json +++ b/data/4.5.4.3/overlay/gem_quality_stats.json @@ -3,8 +3,8 @@ "schema": "gem_quality_stats/v1", "generator": "sync-pob-catalog extract-lua", "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "29ab8262dd4a867eac56165afb8391a509e34a0e", - "vendor_commit_subject": "29ab826 Add support for Darkness Enthroned and Atziri's Splendour augment mods (#2343)", + "vendor_commit": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", + "vendor_commit_subject": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", "extracted_files": [ "Data/Skills/act_dex.lua", "Data/Skills/act_int.lua", @@ -34,6 +34,16 @@ { "stat": "skill_alchemists_boon_generate_x_charges_for_any_flask_per_minute", "per_quality_rate": 0.1 + }, + { + "stat": "alchemists_boon_attack_speed_granted_+%_during_life_flask", + "per_quality_rate": 1.0, + "alt": true + }, + { + "stat": "alchemists_boon_cast_speed_granted_+%_during_mana_flask", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -61,6 +71,11 @@ { "stat": "ancestral_cry_duration_+%_final_per_removable_endurance_charge", "per_quality_rate": 1.0 + }, + { + "stat": "gem_quality_ancestral_cry_global_fire_damage_granted_+%_final", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -79,6 +94,11 @@ { "stat": "base_totem_duration", "per_quality_rate": 100.0 + }, + { + "stat": "totem_life_+%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -97,6 +117,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "animus_exchange_gain_%_maximum_life_spent_as_ward", + "per_quality_rate": 0.25, + "alt": true } ] }, @@ -115,6 +140,11 @@ { "stat": "number_of_chains", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_projectile_damage_+%_final_for_each_remaining_chain", + "per_quality_rate": 0.15, + "alt": true } ] }, @@ -124,6 +154,16 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "archmage_all_damage_%_to_gain_as_lightning_to_grant_to_non_channelling_spells_per_100_max_mana", + "per_quality_rate": 0.05, + "alt": true + }, + { + "stat": "archmage_max_mana_permyriad_to_add_to_non_channelled_spell_mana_cost", + "per_quality_rate": 10.0, + "alt": true } ] }, @@ -142,6 +182,11 @@ { "stat": "maximum_number_of_arctic_armour_stationary_stacks", "per_quality_rate": 0.05 + }, + { + "stat": "arctic_armour_armour_granted_+%_per_stage", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -151,6 +196,11 @@ { "stat": "armour_break_amount_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -160,6 +210,11 @@ { "stat": "armour_break_amount_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -169,6 +224,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "base_number_of_totems_allowed", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -187,6 +247,11 @@ { "stat": "skill_attrition_culling_strike_at_x_or_more_stacks", "per_quality_rate": -0.25 + }, + { + "stat": "presence_area_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -205,6 +270,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "active_skill_projectile_speed_+%_final", + "per_quality_rate": -1.5, + "alt": true } ] }, @@ -214,6 +284,11 @@ { "stat": "barkskin_armour_to_gain_as_%_of_energy_shield_lost_to_enemy_hits", "per_quality_rate": 0.25 + }, + { + "stat": "active_skill_quality_duration_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -223,6 +298,11 @@ { "stat": "empower_barrage_damage_-%_final_with_repeated_projectiles", "per_quality_rate": -0.25 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -232,6 +312,11 @@ { "stat": "gain_x_rage_on_attack_hit", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -250,6 +335,11 @@ { "stat": "channelled_skill_suppress_ongoing_rage_cost_for_first_X_ms", "per_quality_rate": 50.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -259,6 +349,11 @@ { "stat": "skill_base_rage_effect_+%_to_apply", "per_quality_rate": 0.5 + }, + { + "stat": "berserk_maximum_rage_granted_+", + "per_quality_rate": 0.4, + "alt": true } ] }, @@ -268,6 +363,11 @@ { "stat": "volatile_dead_base_number_of_corpses_to_consume", "per_quality_rate": 0.05 + }, + { + "stat": "base_chance_to_not_consume_corpse_%", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -304,6 +404,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 0.5 + }, + { + "stat": "blink_travel_distance", + "per_quality_rate": 1.25, + "alt": true } ] }, @@ -322,6 +427,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "blood_hunt_explosion_%_blood_loss_to_deal_as_life_loss", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -331,6 +441,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -358,6 +473,11 @@ { "stat": "active_skill_pins_as_though_dealt_damage_+%_final", "per_quality_rate": 2.5 + }, + { + "stat": "active_skill_bleeding_effect_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -367,6 +487,11 @@ { "stat": "bone_offering_damage_taken_+%_final_after_shield_loss", "per_quality_rate": -1.5 + }, + { + "stat": "active_skill_quality_duration_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -376,6 +501,11 @@ { "stat": "attack_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -385,6 +515,11 @@ { "stat": "active_skill_cast_speed_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -394,6 +529,11 @@ { "stat": "spells_chance_to_hinder_on_hit_%", "per_quality_rate": 0.25 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 0.3, + "alt": true } ] }, @@ -403,6 +543,11 @@ { "stat": "consume_frenzy_power_and_endurance_charge_every_x_ms", "per_quality_rate": 120.0 + }, + { + "stat": "charge_regulation_damage_per_charge_granted_+%", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -412,6 +557,11 @@ { "stat": "skill_effect_duration_+%", "per_quality_rate": 1.0 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -443,6 +593,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "skill_detonation_time_+%", + "per_quality_rate": -2.0, + "alt": true } ] }, @@ -461,6 +616,11 @@ { "stat": "chance_to_gain_1_more_charge_%", "per_quality_rate": 0.5 + }, + { + "stat": "skill_combat_frenzy_x_ms_cooldown", + "per_quality_rate": -50.0, + "alt": true } ] }, @@ -470,6 +630,11 @@ { "stat": "base_spell_%_chance_to_echo", "per_quality_rate": 0.5 + }, + { + "stat": "damage_+%_vs_frozen_enemies", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -483,6 +648,11 @@ { "stat": "rune_hazard_detonate_time_ms", "per_quality_rate": -5.0 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 0.3, + "alt": true } ] }, @@ -492,6 +662,11 @@ { "stat": "contagion_number_of_additional_targets", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -501,6 +676,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 25.0 + }, + { + "stat": "base_cooldown_speed_+%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -559,6 +739,11 @@ { "stat": "chance_to_gain_1_more_charge_%", "per_quality_rate": 1.0 + }, + { + "stat": "chance_to_gain_1_more_random_charge_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -568,6 +753,11 @@ { "stat": "active_skill_hit_damage_stun_multiplier_+%_final", "per_quality_rate": 2.0 + }, + { + "stat": "active_skill_damage_+%_final_vs_immobilised_enemies", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -577,6 +767,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "base_number_of_totems_allowed", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -595,6 +790,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -613,6 +813,11 @@ { "stat": "curse_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -631,6 +836,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "active_skill_base_physical_damage_%_to_convert_to_fire", + "per_quality_rate": 3.0, + "alt": true } ] }, @@ -640,6 +850,11 @@ { "stat": "corpse_explosion_monster_life_permillage_physical", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -649,6 +864,11 @@ { "stat": "detonating_arrow_max_number_of_stages", "per_quality_rate": 0.05 + }, + { + "stat": "active_skill_ignite_effect_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -667,6 +887,11 @@ { "stat": "disengage_%_chance_for_additional_shockwave", "per_quality_rate": 1.0 + }, + { + "stat": "melee_range_+", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -676,6 +901,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -694,6 +924,11 @@ { "stat": "base_number_of_earthquakes_allowed", "per_quality_rate": 0.1 + }, + { + "stat": "gem_quality_earthquake_damaging_ailment_effect_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -703,6 +938,11 @@ { "stat": "earthshatter_damage_+%_final_per_spike", "per_quality_rate": 0.3 + }, + { + "stat": "slam_aftershock_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -712,6 +952,11 @@ { "stat": "electrocuting_arrow_%_damage_gained_as_extra_lightning_on_debuffed_target", "per_quality_rate": 0.5 + }, + { + "stat": "electrocuting_arrow_damage_taken_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -721,6 +966,11 @@ { "stat": "skill_elemental_conflux_active_element_damage_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "base_skill_effect_duration", + "per_quality_rate": 200.0, + "alt": true } ] }, @@ -748,6 +998,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_critical_strike_chance_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -757,6 +1012,11 @@ { "stat": "curse_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "curse_delay_+%", + "per_quality_rate": -2.5, + "alt": true } ] }, @@ -766,6 +1026,16 @@ { "stat": "ember_fusillade_damage_+%_final_per_ember_fired", "per_quality_rate": 0.1 + }, + { + "stat": "skill_effect_duration_+%", + "per_quality_rate": -2.0, + "alt": true + }, + { + "stat": "active_skill_cast_speed_+%_final", + "per_quality_rate": -1.0, + "alt": true } ] }, @@ -775,6 +1045,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 0.5 + }, + { + "stat": "emergency_reload_damage_+%_final", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -802,6 +1077,11 @@ { "stat": "curse_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -811,6 +1091,11 @@ { "stat": "skill_additional_fissure_chance_%", "per_quality_rate": 1.0 + }, + { + "stat": "base_number_of_entangle_roots_allowed", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -820,6 +1105,11 @@ { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", "per_quality_rate": 6.0 + }, + { + "stat": "damage_+%_vs_frozen_enemies", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -829,6 +1119,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 50.0 + }, + { + "stat": "number_of_chains", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -838,6 +1133,11 @@ { "stat": "skill_eternal_march_maximum_ward_cost", "per_quality_rate": 10.0 + }, + { + "stat": "base_cooldown_speed_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -847,6 +1147,11 @@ { "stat": "ceaseless_rage_base_rage_regeneration_per_minute", "per_quality_rate": 1.5 + }, + { + "stat": "base_spirit_reservation_efficiency_+%", + "per_quality_rate": 0.75, + "alt": true } ] }, @@ -874,6 +1179,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -883,6 +1193,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_number_of_crossbow_bolts", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -901,6 +1216,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -928,6 +1248,11 @@ { "stat": "base_projectile_speed_+%", "per_quality_rate": 1.5 + }, + { + "stat": "projectile_return_%_chance", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -937,6 +1262,11 @@ { "stat": "lightning_strike_damage_+%_final_per_power_charge", "per_quality_rate": 1.0 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -946,6 +1276,11 @@ { "stat": "chance_to_not_consume_parried_%", "per_quality_rate": 0.75 + }, + { + "stat": "melee_range_+", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -982,6 +1317,11 @@ { "stat": "spell_skills_fire_2_additional_projectiles_final_chance_%", "per_quality_rate": 0.5 + }, + { + "stat": "spell_skill_%_chance_to_fire_8_additional_projectiles_in_nova", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -1000,6 +1340,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.1 + }, + { + "stat": "chance_to_not_consume_infusion_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1018,6 +1363,11 @@ { "stat": "active_skill_ignite_chance_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1027,6 +1377,11 @@ { "stat": "active_skill_cast_speed_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "gem_quality_flameblast_damaging_ailment_effect_per_stage_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1036,6 +1391,16 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_base_all_damage_%_to_gain_as_lightning", + "per_quality_rate": 0.5, + "alt": true + }, + { + "stat": "active_skill_base_all_damage_%_to_gain_as_cold", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -1045,6 +1410,11 @@ { "stat": "active_skill_critical_strike_chance_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1054,6 +1424,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_base_all_damage_%_to_gain_as_fire", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1063,6 +1438,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "fortifying_cry_guard_gained_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1072,6 +1452,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "base_secondary_skill_effect_duration", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1081,6 +1466,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "active_skill_reload_speed_+%_final", + "per_quality_rate": 5.0, + "alt": true } ] }, @@ -1108,6 +1498,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "base_skill_effect_duration", + "per_quality_rate": 50.0, + "alt": true } ] }, @@ -1117,6 +1512,11 @@ { "stat": "freezing_mark_hit_damage_freeze_multiplier_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -1126,6 +1526,11 @@ { "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", "per_quality_rate": 2.0 + }, + { + "stat": "base_maximum_seals_for_skill", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -1144,6 +1549,11 @@ { "stat": "active_skill_all_elemental_exposure_compounding_magnitude_cap", "per_quality_rate": 0.5 + }, + { + "stat": "base_maximum_number_of_frost_bombs", + "per_quality_rate": 0.15, + "alt": true } ] }, @@ -1153,6 +1563,11 @@ { "stat": "spell_skills_fire_2_additional_projectiles_final_chance_%", "per_quality_rate": 0.5 + }, + { + "stat": "number_of_additional_forks_base", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -1162,6 +1577,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "ice_crystal_maximum_life_+%", + "per_quality_rate": 20.0, + "alt": true } ] }, @@ -1171,6 +1591,11 @@ { "stat": "active_skill_projectile_damage_+%_final_if_pierced_enemy", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_projectile_speed_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1180,6 +1605,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.5 + }, + { + "stat": "damage_+%_vs_frozen_enemies", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1189,6 +1619,11 @@ { "stat": "active_skill_chill_effect_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "base_number_of_frozen_locus_allowed", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -1216,6 +1651,11 @@ { "stat": "active_skill_stun_threshold_+%_while_performing_action", "per_quality_rate": 2.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1234,6 +1674,11 @@ { "stat": "wind_blast_damage_+%_final_from_distance", "per_quality_rate": 1.0 + }, + { + "stat": "armour_break_physical_damage_%_dealt_as_armour_break", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1252,6 +1697,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_number_of_crossbow_bolts", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -1270,6 +1720,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1279,6 +1734,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -1288,6 +1748,11 @@ { "stat": "perfect_timing_window_ms_+%", "per_quality_rate": 1.0 + }, + { + "stat": "chance_for_extra_damage_roll_with_lightning_damage_%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -1306,6 +1771,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 1.0 + }, + { + "stat": "ghost_dance_max_stacks", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -1324,6 +1794,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -1333,6 +1808,11 @@ { "stat": "active_skill_base_physical_damage_%_to_gain_as_cold", "per_quality_rate": 0.75 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1342,6 +1822,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1351,6 +1836,11 @@ { "stat": "grim_remnant_health_stored_in_globe_%", "per_quality_rate": 0.25 + }, + { + "stat": "remnant_pickup_range_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1360,6 +1850,11 @@ { "stat": "grim_pillars_number_of_pillars_to_create", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_base_secondary_area_of_effect_radius", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -1378,6 +1873,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_reduce_enemy_cold_resistance_%", + "per_quality_rate": 3.0, + "alt": true } ] }, @@ -1387,6 +1887,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "chance_to_not_consume_glory_%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -1414,6 +1919,11 @@ { "stat": "herald_of_ash_burning_%_overkill_damage_per_minute", "per_quality_rate": 15.0 + }, + { + "stat": "herald_of_ash_fire_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1423,6 +1933,11 @@ { "stat": "skill_herald_bleeding_enemies_explode_for_%_blood_loss_as_unscalable_physical_damage", "per_quality_rate": 0.2 + }, + { + "stat": "herald_of_blood_global_physical_damage_granted_+%", + "per_quality_rate": 0.75, + "alt": true } ] }, @@ -1432,6 +1947,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "herald_of_ice_cold_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1441,6 +1961,11 @@ { "stat": "hinder_chance_%_on_spreading_poioson", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -1450,6 +1975,11 @@ { "stat": "herald_of_thunder_storm_max_hits", "per_quality_rate": 0.1 + }, + { + "stat": "herald_of_thunder_lightning_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1459,6 +1989,11 @@ { "stat": "hexblast_damage_+%_final_per_second_remaining_curse_duration", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_critical_strike_chance_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1468,6 +2003,11 @@ { "stat": "active_skill_projectile_damage_+%_final_if_pierced_enemy", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1513,6 +2053,11 @@ { "stat": "skill_hollow_shell_spent_ward_%_to_grant_as_guard", "per_quality_rate": 0.25 + }, + { + "stat": "base_skill_effect_duration", + "per_quality_rate": 100.0, + "alt": true } ] }, @@ -1531,6 +2076,11 @@ { "stat": "active_skill_damage_+%_final_vs_chilled_enemies", "per_quality_rate": 1.0 + }, + { + "stat": "chance_to_not_consume_infusion_%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1540,6 +2090,11 @@ { "stat": "active_skill_chill_as_though_damage_+%_final", "per_quality_rate": 5.0 + }, + { + "stat": "active_skill_hit_damage_freeze_multiplier_+%_final", + "per_quality_rate": 10.0, + "alt": true } ] }, @@ -1549,6 +2104,11 @@ { "stat": "active_skill_chill_effect_+%_final", "per_quality_rate": 1.5 + }, + { + "stat": "movement_speed_penalty_+%_while_performing_action", + "per_quality_rate": -2.0, + "alt": true } ] }, @@ -1558,6 +2118,11 @@ { "stat": "attack_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_critical_strike_chance_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1567,6 +2132,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 0.5 + }, + { + "stat": "shearing_bolts_number_of_empowered_attacks", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -1594,6 +2164,11 @@ { "stat": "ignite_duration_+%_per_shotgun_pellet_hit", "per_quality_rate": 0.75 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -1603,6 +2178,11 @@ { "stat": "active_skill_ignite_duration_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "incinerate_maximum_fuel", + "per_quality_rate": 100.0, + "alt": true } ] }, @@ -1621,6 +2201,11 @@ { "stat": "infernal_cry_exerted_attack_all_damage_%_to_gain_as_fire_%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -1639,6 +2224,11 @@ { "stat": "active_skill_hit_damage_stun_multiplier_+%_final", "per_quality_rate": 2.5 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1648,6 +2238,11 @@ { "stat": "recover_%_maximum_mana_on_cull", "per_quality_rate": 0.2 + }, + { + "stat": "recover_%_maximum_life_on_cull", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -1666,6 +2261,11 @@ { "stat": "active_skill_damage_+%_final_vs_fully_broken_armour", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1675,6 +2275,11 @@ { "stat": "skill_leylines_spell_damage_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -1693,6 +2298,11 @@ { "stat": "active_skill_shock_chance_+%_final", "per_quality_rate": 2.0 + }, + { + "stat": "chance_for_extra_damage_roll_with_lightning_damage_%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -1711,6 +2321,11 @@ { "stat": "active_skill_lightning_damage_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "lightning_conduit_x_additional_strikes_if_consumed_a_shock", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -1720,6 +2335,11 @@ { "stat": "lightning_rod_%_chance_for_additional_burst_on_landing", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1729,6 +2349,11 @@ { "stat": "base_number_of_projectiles", "per_quality_rate": 0.1 + }, + { + "stat": "chance_%_to_double_effect_of_removing_charges", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1738,6 +2363,11 @@ { "stat": "shock_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_cast_speed_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1747,6 +2377,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_spirit_reservation_efficiency_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1774,6 +2409,16 @@ { "stat": "active_skill_ignite_chance_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 2.5, + "alt": true + }, + { + "stat": "living_bomb_damage_threshold_%_of_ailment_threshold", + "per_quality_rate": 10.0, + "alt": true } ] }, @@ -1783,6 +2428,11 @@ { "stat": "active_skill_base_physical_damage_%_to_gain_as_cold", "per_quality_rate": 0.5 + }, + { + "stat": "base_reduce_enemy_cold_resistance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1792,6 +2442,11 @@ { "stat": "skill_igneous_shield_grants_block_chance_+%", "per_quality_rate": 0.25 + }, + { + "stat": "magma_barrier_maximum_block_chance_granted_%", + "per_quality_rate": 0.25, + "alt": true } ] }, @@ -1801,6 +2456,11 @@ { "stat": "base_number_of_projectiles", "per_quality_rate": 0.2 + }, + { + "stat": "active_skill_lightning_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1828,6 +2488,11 @@ { "stat": "mana_remnants_mana_gain_per_globe", "per_quality_rate": 2.0 + }, + { + "stat": "mana_remnants_global_mana_regeneration_rate_granted_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1837,6 +2502,11 @@ { "stat": "mana_tempest_effects_linger_X_ms", "per_quality_rate": 100.0 + }, + { + "stat": "mana_tempest_mana_cost_%_to_add_to_cost_per_second", + "per_quality_rate": -0.4, + "alt": true } ] }, @@ -1855,6 +2525,11 @@ { "stat": "active_skill_required_number_of_combo_stacks", "per_quality_rate": -0.05 + }, + { + "stat": "base_secondary_skill_effect_duration", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -1882,6 +2557,11 @@ { "stat": "base_skill_cost_efficiency_+%", "per_quality_rate": 1.0 + }, + { + "stat": "skill_effect_duration_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1891,6 +2571,11 @@ { "stat": "energy_generated_+%", "per_quality_rate": 0.75 + }, + { + "stat": "triggered_skill_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1936,6 +2621,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "cast_on_crit_global_critical_hit_chance_granted_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1945,6 +2635,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "cast_on_dodge_dodge_roll_distance_granted_+", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -1954,6 +2649,11 @@ { "stat": "energy_generated_+%", "per_quality_rate": 0.75 + }, + { + "stat": "base_reservation_efficiency_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -1963,6 +2663,11 @@ { "stat": "energy_generated_+%", "per_quality_rate": 1.0 + }, + { + "stat": "triggered_skill_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1981,6 +2686,11 @@ { "stat": "generic_ongoing_trigger_maximum_energy", "per_quality_rate": 5.0 + }, + { + "stat": "triggered_skill_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -1990,6 +2700,11 @@ { "stat": "energy_generated_+%", "per_quality_rate": 1.0 + }, + { + "stat": "base_reservation_efficiency_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2008,6 +2723,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "base_reservation_efficiency_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2017,6 +2737,11 @@ { "stat": "totem_skill_area_of_effect_+%", "per_quality_rate": 1.0 + }, + { + "stat": "base_number_of_totems_allowed", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -2026,6 +2751,11 @@ { "stat": "energy_generated_+%", "per_quality_rate": 1.0 + }, + { + "stat": "triggered_skill_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2039,6 +2769,11 @@ { "stat": "ward_remnants_chance_to_spawn_remnant_on_stun_%", "per_quality_rate": 0.25 + }, + { + "stat": "ward_remnants_spawn_remnant_on_stun_cooldown_ms", + "per_quality_rate": -2.5, + "alt": true } ] }, @@ -2075,6 +2810,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 50.0 + }, + { + "stat": "base_reservation_efficiency_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2102,6 +2842,11 @@ { "stat": "inflict_exposure_on_hit_%_chance", "per_quality_rate": 1.5 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2138,6 +2883,11 @@ { "stat": "skill_base_oil_exposure_-_to_total_elemental_resistance", "per_quality_rate": 0.25 + }, + { + "stat": "charge_skip_consume_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2147,6 +2897,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "skill_base_oil_exposure_-_to_total_elemental_resistance", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -2156,6 +2911,11 @@ { "stat": "number_of_chains", "per_quality_rate": 0.15 + }, + { + "stat": "orb_of_storms_maximum_number_of_hits", + "per_quality_rate": 0.3, + "alt": true } ] }, @@ -2174,6 +2934,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 1.2 + }, + { + "stat": "pain_offering_attack_and_cast_speed_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2192,6 +2957,11 @@ { "stat": "active_skill_ignite_chance_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2201,6 +2971,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "active_skill_reload_speed_+%_final", + "per_quality_rate": 5.0, + "alt": true } ] }, @@ -2237,6 +3012,11 @@ { "stat": "plague_bearer_gains_%_of_damage_from_inflicted_poisons", "per_quality_rate": 0.1 + }, + { + "stat": "poison_effect_+%_vs_non_poisoned_enemies", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -2246,6 +3026,11 @@ { "stat": "active_skill_shock_duration_+%_final", "per_quality_rate": 1.5 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2255,6 +3040,11 @@ { "stat": "active_skill_poison_effect_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "armour_break_for_%_of_poison_damage_over_poison_duration", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2273,6 +3063,11 @@ { "stat": "verisium_infusion_duration_+%", "per_quality_rate": 2.5 + }, + { + "stat": "additional_infusion_gain_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2291,6 +3086,11 @@ { "stat": "attack_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_critical_strike_chance_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2300,6 +3100,11 @@ { "stat": "base_chance_to_not_consume_corpse_%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_quality_duration_+%_final", + "per_quality_rate": 5.0, + "alt": true } ] }, @@ -2336,6 +3141,11 @@ { "stat": "base_number_of_raging_spirits_allowed", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_minion_damage_+%_final", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -2345,6 +3155,11 @@ { "stat": "rain_of_arrows_arrow_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2363,6 +3178,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "hit_damage_immobilisation_multiplier_+%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2372,6 +3192,11 @@ { "stat": "raise_zombie_empowerment_effect_+%", "per_quality_rate": 1.25 + }, + { + "stat": "base_number_of_zombies_allowed", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -2381,6 +3206,11 @@ { "stat": "base_additional_damage_from_distance_+%_final", "per_quality_rate": 1.5 + }, + { + "stat": "base_aggravate_bleeding_on_attack_hit_chance_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2390,6 +3220,11 @@ { "stat": "rapid_assault_%_chance_to_attach_additional_spear", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_bleeding_effect_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2399,6 +3234,11 @@ { "stat": "maximum_number_of_crossbow_heat_stacks", "per_quality_rate": 0.75 + }, + { + "stat": "maximum_number_of_crossbow_heat_stacks", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2408,6 +3248,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.5 + }, + { + "stat": "base_cooldown_modifiable_repeat_interval_ms", + "per_quality_rate": -100.0, + "alt": true } ] }, @@ -2426,6 +3271,11 @@ { "stat": "runic_fortress_stun_threshold_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "base_skill_effect_duration", + "per_quality_rate": 50.0, + "alt": true } ] }, @@ -2435,6 +3285,11 @@ { "stat": "armour_break_amount_+%", "per_quality_rate": 1.0 + }, + { + "stat": "stun_threshold_+%_while_channelling", + "per_quality_rate": 5.0, + "alt": true } ] }, @@ -2444,6 +3299,11 @@ { "stat": "active_skill_minion_life_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "base_reservation_efficiency_+%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2471,6 +3331,11 @@ { "stat": "active_skill_damage_+%_final_per_time_chained", "per_quality_rate": 0.5 + }, + { + "stat": "number_of_chains", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -2480,6 +3345,11 @@ { "stat": "active_skill_stun_threshold_+%_while_performing_action", "per_quality_rate": 5.0 + }, + { + "stat": "armour_break_physical_damage_%_dealt_as_armour_break", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2489,6 +3359,11 @@ { "stat": "active_skill_stun_threshold_+%_while_performing_action", "per_quality_rate": 2.0 + }, + { + "stat": "rune_ward_block_%_damage_taken", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -2507,6 +3382,11 @@ { "stat": "harvester_minion_resummon_speed_+%_final", "per_quality_rate": 0.25 + }, + { + "stat": "sacrifice_minion_life_granted_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2525,6 +3405,11 @@ { "stat": "wild_beast_damage_+%_final", "per_quality_rate": 0.25 + }, + { + "stat": "wild_beast_energy_per_hit", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -2543,6 +3428,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 100.0 + }, + { + "stat": "scavenged_plating_armour_+%_final_per_stack", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -2552,6 +3442,11 @@ { "stat": "seismic_cry_slam_skill_aftershock_damage_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_base_physical_damage_%_to_gain_as_fire", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2561,6 +3456,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_quality_duration_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2579,6 +3479,11 @@ { "stat": "active_skill_critical_strike_chance_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2588,6 +3493,11 @@ { "stat": "active_skill_critical_strike_chance_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2606,6 +3516,11 @@ { "stat": "base_additional_damage_from_distance_+%_final", "per_quality_rate": 2.0 + }, + { + "stat": "shield_charge_extra_distance", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2615,6 +3530,11 @@ { "stat": "shield_wall_damage_+%_final_when_slammed", "per_quality_rate": 2.0 + }, + { + "stat": "active_skill_base_physical_damage_%_to_gain_as_fire", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2624,6 +3544,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_lightning_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2642,6 +3567,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2651,6 +3581,11 @@ { "stat": "active_skill_lightning_damage_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2660,6 +3595,11 @@ { "stat": "base_totem_duration", "per_quality_rate": 200.0 + }, + { + "stat": "base_number_of_totems_allowed", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -2669,6 +3609,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_number_of_totems_allowed", + "per_quality_rate": 0.05, + "alt": true } ] }, @@ -2687,6 +3632,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_number_of_crossbow_bolts", + "per_quality_rate": 0.15, + "alt": true } ] }, @@ -2696,6 +3646,11 @@ { "stat": "active_skill_damage_+%_final_vs_immobilised_enemies", "per_quality_rate": 2.0 + }, + { + "stat": "base_number_of_crossbow_bolts", + "per_quality_rate": 0.005, + "alt": true } ] }, @@ -2722,6 +3677,11 @@ { "stat": "infusion_remnants_%_chance_to_spawn_lightning_infusion_on_shocking_an_enemy", "per_quality_rate": 0.1 + }, + { + "stat": "remnant_pickup_range_+%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2731,6 +3691,11 @@ { "stat": "base_chance_to_daze_%", "per_quality_rate": 2.5 + }, + { + "stat": "chance_to_gain_1_more_charge_%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -2740,6 +3705,11 @@ { "stat": "base_spell_%_chance_to_echo", "per_quality_rate": 0.5 + }, + { + "stat": "damage_+%_vs_frozen_enemies", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2749,6 +3719,11 @@ { "stat": "chance_%_to_spawn_another_infusion_remnant", "per_quality_rate": 0.5 + }, + { + "stat": "base_cooldown_speed_+%", + "per_quality_rate": 5.0, + "alt": true } ] }, @@ -2758,6 +3733,16 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true + }, + { + "stat": "perfect_timing_window_ms_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2767,6 +3752,11 @@ { "stat": "enemy_additional_critical_strike_multiplier_against_self", "per_quality_rate": 0.75 + }, + { + "stat": "gem_quality_marked_enemy_damage_dealt_+%_final", + "per_quality_rate": -0.5, + "alt": true } ] }, @@ -2794,6 +3784,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 50.0 + }, + { + "stat": "power_offering_buff_spell_damage_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2812,6 +3807,11 @@ { "stat": "base_projectile_speed_+%", "per_quality_rate": 1.5 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -2821,6 +3821,11 @@ { "stat": "chance_to_retain_40%_of_glory_on_use_%", "per_quality_rate": 1.25 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2830,6 +3835,11 @@ { "stat": "hazard_rearm_%_chance", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2839,6 +3849,11 @@ { "stat": "base_number_of_projectiles", "per_quality_rate": 0.2 + }, + { + "stat": "chance_%_to_double_effect_of_removing_charges", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -2866,6 +3881,11 @@ { "stat": "attacks_chance_to_blind_on_hit_%", "per_quality_rate": 2.0 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2875,6 +3895,11 @@ { "stat": "slam_aftershock_chance_%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2893,6 +3918,11 @@ { "stat": "base_number_of_overcharged_spears_allowed", "per_quality_rate": 0.05 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2902,6 +3932,11 @@ { "stat": "active_skill_base_secondary_area_of_effect_radius", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_base_area_of_effect_radius", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -2911,6 +3946,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_number_of_crossbow_bolts", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2929,6 +3969,11 @@ { "stat": "shock_duration_+%", "per_quality_rate": 1.5 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2947,6 +3992,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_minion_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -2983,6 +4033,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "base_number_of_projectiles", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -2992,6 +4047,11 @@ { "stat": "active_skill_minion_hit_damage_stun_multiplier_+%_final", "per_quality_rate": 2.0 + }, + { + "stat": "active_skill_minion_life_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -3001,6 +4061,11 @@ { "stat": "skeletal_cleric_revived_skeletons_immune_for_X_ms", "per_quality_rate": 150.0 + }, + { + "stat": "minion_damage_taken_+%", + "per_quality_rate": -2.0, + "alt": true } ] }, @@ -3010,6 +4075,11 @@ { "stat": "active_skill_minion_damage_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "base_spirit_reservation_efficiency_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3019,6 +4089,11 @@ { "stat": "minion_rage_effect_+%", "per_quality_rate": 1.0 + }, + { + "stat": "armour_break_physical_damage_%_dealt_as_armour_break", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -3037,6 +4112,11 @@ { "stat": "minion_maximum_life_%_to_gain_as_maximum_energy_shield", "per_quality_rate": 1.0 + }, + { + "stat": "minion_cast_speed_+%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -3055,6 +4135,11 @@ { "stat": "active_skill_minion_damage_+%_final", "per_quality_rate": 1.0 + }, + { + "stat": "base_reservation_efficiency_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3073,6 +4158,11 @@ { "stat": "accuracy_range_penalty_+%", "per_quality_rate": -2.5 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3082,6 +4172,11 @@ { "stat": "channelled_slam_max_stages", "per_quality_rate": 0.05 + }, + { + "stat": "active_skill_hit_damage_stun_multiplier_+%_final", + "per_quality_rate": 15.0, + "alt": true } ] }, @@ -3091,6 +4186,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "curse_effect_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3100,6 +4200,11 @@ { "stat": "warcry_grant_damage_+%_to_exerted_attacks", "per_quality_rate": 1.5 + }, + { + "stat": "base_cooldown_speed_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3113,6 +4218,11 @@ { "stat": "mark_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3122,6 +4232,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "totems_spells_cast_speed_+%_per_active_totem", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -3140,6 +4255,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 80.0 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3158,6 +4278,11 @@ { "stat": "tempest_bell_damage_+%_final_per_time_hit", "per_quality_rate": 0.15 + }, + { + "stat": "bell_hit_limit", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -3167,6 +4292,11 @@ { "stat": "melee_range_+", "per_quality_rate": 0.2 + }, + { + "stat": "final_strike_number_of_spirit_strikes", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -3176,6 +4306,11 @@ { "stat": "curse_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "curse_delay_+%", + "per_quality_rate": -2.5, + "alt": true } ] }, @@ -3203,6 +4338,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.25 + }, + { + "stat": "impale_on_hit_%_chance", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -3212,6 +4352,11 @@ { "stat": "thunderous_leap_%_chance_for_shocked_ground_when_detonating", "per_quality_rate": 1.5 + }, + { + "stat": "slam_aftershock_chance_%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3225,6 +4370,11 @@ { "stat": "thunderstorm_wet_debuff_hit_damage_freeze_multiplier_+%_final", "per_quality_rate": 0.25 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3243,6 +4393,11 @@ { "stat": "skill_time_of_need_gain_x_life", "per_quality_rate": 2.0 + }, + { + "stat": "time_of_need_global_life_regeneration_rate_granted_+%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3261,6 +4416,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 100.0 + }, + { + "stat": "number_of_tornados_allowed", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -3270,6 +4430,11 @@ { "stat": "tornado_shot_projectile_damage_+%_final", "per_quality_rate": 0.75 + }, + { + "stat": "base_number_of_tornado_shots_allowed", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -3279,6 +4444,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.15 + }, + { + "stat": "toxic_domain_buff_linger_duration_ms", + "per_quality_rate": 100.0, + "alt": true } ] }, @@ -3288,6 +4458,11 @@ { "stat": "base_cooldown_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3301,6 +4476,11 @@ { "stat": "number_of_poisonbloom_arrow_bloom_allowed", "per_quality_rate": 0.05 + }, + { + "stat": "base_poison_duration_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3310,6 +4490,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.1 + }, + { + "stat": "base_ward_cost_efficiency_+%", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -3319,6 +4504,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 100.0 + }, + { + "stat": "base_number_of_verisium_runes_allowed", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -3337,6 +4527,11 @@ { "stat": "maximum_caltrops_allowed", "per_quality_rate": 0.25 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3346,6 +4541,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "wind_dancer_damage_+%_final_per_stage", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3355,6 +4555,11 @@ { "stat": "trinity_skill_speed_+%_while_all_resonance_is_at_least_250_to_grant", "per_quality_rate": 0.75 + }, + { + "stat": "trinity_%_physical_damage_to_convert_to_random_element_granted", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3364,6 +4569,11 @@ { "stat": "triskelion_cascade_next_skill_damage_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "triskelion_cascade_next_skill_area_of_effect_+%_final", + "per_quality_rate": 0.25, + "alt": true } ] }, @@ -3373,6 +4583,11 @@ { "stat": "twister_%_chance_for_additional_twister", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_projectile_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3395,6 +4610,11 @@ { "stat": "minion_movement_speed_+%", "per_quality_rate": 2.5 + }, + { + "stat": "active_skill_minion_attack_speed_+%_final", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3431,6 +4651,11 @@ { "stat": "broken_stance_hit_limit", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_damage_+%_final_against_heavy_stunned_enemies", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3449,6 +4674,11 @@ { "stat": "poison_vine_arrow_vine_stored_poison_damage_+%_final", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_quality_duration_+%_final", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3498,6 +4728,11 @@ { "stat": "slam_aftershock_chance_%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_fire_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3507,6 +4742,11 @@ { "stat": "active_skill_base_area_of_effect_radius", "per_quality_rate": 0.1 + }, + { + "stat": "active_skill_quality_duration_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3525,6 +4765,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.0 + }, + { + "stat": "number_of_chains", + "per_quality_rate": 0.1, + "alt": true } ] }, @@ -3534,6 +4779,11 @@ { "stat": "thaumaturgist_mark_hit_damage_electrocute_multiplier_+%", "per_quality_rate": 1.0 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3543,6 +4793,11 @@ { "stat": "curse_effect_+%", "per_quality_rate": 0.5 + }, + { + "stat": "base_mana_cost_-%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3552,6 +4807,11 @@ { "stat": "walking_calamity_non_skill_base_all_damage_%_to_gain_as_fire", "per_quality_rate": 0.5 + }, + { + "stat": "chance_to_not_consume_glory_%", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3561,6 +4821,11 @@ { "stat": "base_reservation_efficiency_+%", "per_quality_rate": 0.5 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.5, + "alt": true } ] }, @@ -3570,6 +4835,11 @@ { "stat": "wardbound_minion_maximum_number_of_casts", "per_quality_rate": 0.1 + }, + { + "stat": "base_skill_effect_duration", + "per_quality_rate": 250.0, + "alt": true } ] }, @@ -3588,6 +4858,11 @@ { "stat": "base_reduce_enemy_cold_resistance_%", "per_quality_rate": 1.0 + }, + { + "stat": "frozen_monsters_take_increased_damage", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3606,6 +4881,11 @@ { "stat": "attack_speed_+%", "per_quality_rate": 1.0 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3615,6 +4895,11 @@ { "stat": "sandstorm_swipe_storm_damage_+%_final_per_stage", "per_quality_rate": 2.0 + }, + { + "stat": "active_skill_attack_speed_+%_final", + "per_quality_rate": 0.5, + "alt": true } ] }, @@ -3624,6 +4909,11 @@ { "stat": "sandstorm_swipe_storm_damage_+%_final_per_stage", "per_quality_rate": 2.0 + }, + { + "stat": "windstorm_gain_all_damage_%_as_corresponding_element_if_empowered", + "per_quality_rate": 2.5, + "alt": true } ] }, @@ -3646,6 +4936,11 @@ { "stat": "active_skill_knockback_distance_+%_final", "per_quality_rate": 1.5 + }, + { + "stat": "active_skill_base_all_damage_%_to_gain_as_chaos", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3655,6 +4950,11 @@ { "stat": "wing_blast_chance_to_gain_power_charge_on_stun_%", "per_quality_rate": 0.25 + }, + { + "stat": "active_skill_physical_damage_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3664,6 +4964,11 @@ { "stat": "base_skill_effect_duration", "per_quality_rate": 50.0 + }, + { + "stat": "skill_withering_presence_frequency_ms", + "per_quality_rate": -25.0, + "alt": true } ] }, @@ -3673,6 +4978,11 @@ { "stat": "wolf_warcry_buff_freeze_buildup_+%", "per_quality_rate": 1.0 + }, + { + "stat": "chance_for_exerted_attacks_to_not_reduce_count_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3682,6 +4992,11 @@ { "stat": "active_skill_base_secondary_area_of_effect_radius", "per_quality_rate": 0.2 + }, + { + "stat": "active_skill_base_physical_damage_%_to_convert_to_cold", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3691,6 +5006,11 @@ { "stat": "wolf_lunar_blessing_all_damage_%_to_gain_as_cold_damage", "per_quality_rate": 0.5 + }, + { + "stat": "base_cooldown_speed_+%", + "per_quality_rate": 2.0, + "alt": true } ] }, @@ -3700,6 +5020,11 @@ { "stat": "skill_wolf_pack_size", "per_quality_rate": 0.05 + }, + { + "stat": "active_skill_minion_attack_speed_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3722,6 +5047,11 @@ { "stat": "dummy_stat_display_nothing", "per_quality_rate": 0.001 + }, + { + "stat": "number_of_wolves_allowed", + "per_quality_rate": 0.2, + "alt": true } ] }, @@ -3740,6 +5070,11 @@ { "stat": "wyvern_devour_flask_charm_charge_gain_chance_%", "per_quality_rate": 2.5 + }, + { + "stat": "chance_to_gain_1_more_charge_%", + "per_quality_rate": 1.0, + "alt": true } ] }, @@ -3749,6 +5084,11 @@ { "stat": "channelled_skill_suppress_ongoing_rage_cost_for_first_X_ms", "per_quality_rate": 50.0 + }, + { + "stat": "active_skill_area_of_effect_+%_final", + "per_quality_rate": 1.0, + "alt": true } ] }, diff --git a/tools/sync-pob-catalog/src/extract_gem_quality.lua b/tools/sync-pob-catalog/src/extract_gem_quality.lua index f97ceb8f..7918ba66 100644 --- a/tools/sync-pob-catalog/src/extract_gem_quality.lua +++ b/tools/sync-pob-catalog/src/extract_gem_quality.lua @@ -105,19 +105,28 @@ end -- 抽取并输出 JSONL(pairs 顺序不确定没关系:Rust 侧按 effect_id 排序, -- 单个效果内的行经 ipairs 保持 vendor 顺序且在输出中天然连续成组) ---------------------------------------------------------------------- +-- altQualityStats(vendor CalcTools.lua:147-152):仅 GemlingQuality flag build +-- 生效的附加品质 stat,带 "alt":true 标记转录(消费侧按 flag 门控)。 for skillId, skill in pairs(skills) do - if type(skill) == "table" and type(skill.qualityStats) == "table" then - for _, qs in ipairs(skill.qualityStats) do - if type(qs) == "table" and type(qs[1]) == "string" and type(qs[2]) == "number" then - print( - '{"effect":"' - .. jsonEscape(skillId) - .. '","stat":"' - .. jsonEscape(qs[1]) - .. '","rate":' - .. jsonNum(qs[2]) - .. "}" - ) + if type(skill) == "table" then + for _, field in ipairs({ "qualityStats", "altQualityStats" }) do + local list = skill[field] + if type(list) == "table" then + local altSuffix = field == "altQualityStats" and ',"alt":true' or "" + for _, qs in ipairs(list) do + if type(qs) == "table" and type(qs[1]) == "string" and type(qs[2]) == "number" then + print( + '{"effect":"' + .. jsonEscape(skillId) + .. '","stat":"' + .. jsonEscape(qs[1]) + .. '","rate":' + .. jsonNum(qs[2]) + .. altSuffix + .. "}" + ) + end + end end end end diff --git a/tools/sync-pob-catalog/src/extract_quality.rs b/tools/sync-pob-catalog/src/extract_quality.rs index 5f03da09..2f8e6efb 100644 --- a/tools/sync-pob-catalog/src/extract_quality.rs +++ b/tools/sync-pob-catalog/src/extract_quality.rs @@ -43,6 +43,9 @@ pub struct QualityRow { pub stat: String, /// 每 1 点品质的斜率(vendor 数据已 `/1000`,原样转录)。 pub rate: f64, + /// vendor `altQualityStats` 条目(仅 GemlingQuality flag build 生效)。 + #[serde(default)] + pub alt: bool, } /// 完整 overlay 文档(生成侧;消费侧 schema 见 @@ -73,6 +76,7 @@ pub fn assemble_quality_document(meta: OverlayMeta, rows: Vec) -> St by_effect.entry(row.effect).or_default().push(QualityStat { stat: row.stat, per_quality_rate: row.rate, + alt: row.alt, }); } let effects = by_effect diff --git a/tools/sync-pob-catalog/tests/extract/extract_quality.rs b/tools/sync-pob-catalog/tests/extract/extract_quality.rs index 8020acf5..5aaa226a 100644 --- a/tools/sync-pob-catalog/tests/extract/extract_quality.rs +++ b/tools/sync-pob-catalog/tests/extract/extract_quality.rs @@ -106,6 +106,7 @@ fn assemble_document_is_deterministic_and_sorted_by_effect() { effect: effect.to_string(), stat: stat.to_string(), rate, + alt: false, }; // 两种到达顺序(effect 层乱序,单效果内顺序一致——对齐 Lua 侧 pairs 外层 // 不确定 / ipairs 内层连续的输出形态)。 From 1d2ba56a2dc214630b2baa1828805d94ee081deb Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:38:46 +0800 Subject: [PATCH 59/74] fix(defence): model enemy Intimidated base pair behind presence condition What changed - setup_enemy injects the vendor actor-init conditional pair on the enemy modDB: Damage INC -10 / DamageTaken INC 10 gated on EnemyIntimidated (CalcSetup.lua:73-77). - env_finalize stage 7.5 bridges an enemy-db Condition:Intimidated FLAG (e.g. item mod "Enemies in your Presence are Intimidated") into cfg EnemyIntimidated (vendor ModStore GetCondition semantics; allow-list = Intimidated only). - Orchestrator seeds EnemyInPresence true (CalcPerform.lua:524 default; PresenceRadius vs enemyDistance not modelled). Why - wolf-pack's uniform ~10% per-type taken-multiplier gap: oracle pins EnemyDamageMult = 0.9 from the Intimidated base mod lit by the body armour's corrupted implicit; PoBR had the flag in the enemy db but no consumer. The max-hit end divisor and EHP damage-in multiplier were already wired - only the mod was missing. Validation - wolf-pack Phys/Fire/Cold/Light MaxHit 0.90x -> 1.00x, PhysDR 65.69 -> 68.03 (oracle 68), TotalEHP 0.83x -> 0.98x. No other build's cells move (per-cell 18-build diff). --- .../pobr-build/src/calc_orchestrator/mod.rs | 8 ++++++ crates/pobr-core/src/calc/env_finalize.rs | 26 +++++++++++++++++++ crates/pobr-core/src/calc/setup_env.rs | 15 +++++++++++ 3 files changed, 49 insertions(+) diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index 85fd958c..cb420cd0 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -863,6 +863,14 @@ fn stage_build_cfg(ctx: &mut StageCtx<'_>) { if main_hand_offhand_is_shield(build, data) { cfg = cfg.with_condition("UsingShield", true); } + // 敌人在 Presence 内(vendor CalcPerform.lua:524 `condList["EnemyInPresence"] + // = PresenceRadius >= enemyDistance`):默认 Presence 半径(数米级)恒大于默认 + // 敌人距离 → 默认真,使「Enemies in your Presence ...」族敌侧词条生效。 + // ponytail: pobr 未建模 PresenceRadius/enemyDistance 数值比较,恒置真;用户 + // 拉远 enemyDistance 的口径差留 parity 点名再接。 + if !cfg.conditions.contains_key("EnemyInPresence") { + cfg = cfg.with_condition("EnemyInPresence", true); + } // 伙伴在场条件(vendor ConfigOptions.lua:1012-1014 `companionInPresence` // defaultState=true,ifSkillType=CreatesCompanion 门控):已启用技能含 // `CreatesCompanion` 时默认置真,使「while your Companion is in your diff --git a/crates/pobr-core/src/calc/env_finalize.rs b/crates/pobr-core/src/calc/env_finalize.rs index 2d31e169..2155f7c5 100644 --- a/crates/pobr-core/src/calc/env_finalize.rs +++ b/crates/pobr-core/src/calc/env_finalize.rs @@ -45,6 +45,13 @@ pub fn env_finalize(env: &mut Env) { expand_misc_buffs(env); // 阶段 7(T4):非伤害异常施加(Chill/Shock → enemy db)。 apply_nondamaging_ailments(env); + // 阶段 7.5:敌侧条件 flag → cfg 条件桥接(vendor ModStore GetCondition 语义: + // enemyDB 的 `Condition:` FLAG 使敌人条件 为真)。pobr 条件统一放 + // cfg.conditions(敌侧键空间 = `Enemy`),故在全部敌侧注入源(item + // EnemyModifier 转发 / buff_pass / 异常)落库后统一回填。 + // ponytail: 允收名单只有 Intimidated(消费方 = setup_enemy 注入的敌人基础 + // 条件对);其余 Condition:* flag 已各有专用桥(config/异常),需要时逐条扩。 + bridge_enemy_condition_flags(env); // 阶段 8(M4-L):曝光归约(vendor CalcPerform.lua:3214-3247 "Apply // exposures",buff 循环之后 / offence 之前)——enemy db 内全部 // `Exposure BASE`(config 注入 + buff_pass Debuff 路径,如 Frost Bomb) @@ -453,6 +460,25 @@ pub fn apply_nondamaging_ailments(env: &mut Env) { super::ailment_apply::apply_nondamaging_ailments(env); } +/// 阶段 7.5:敌侧 `Condition:` FLAG → `cfg.conditions["Enemy"]` 桥接。 +/// +/// vendor 语义(ModStore.lua `GetCondition`):条件真值 = conditions 表 **或** +/// modDB `Condition:` FLAG 聚合——敌人被词条施加的条件态(如体甲「Enemies in +/// your Presence are Intimidated」→ enemy db `Condition:Intimidated` flag)等价 +/// 于 config 勾选。pobr 的条件消费统一走 `cfg.conditions`,此处把 flag 聚合结果 +/// 回填(`matches(cfg)` 已含 Effective/EnemyInPresence 等 tag 门控)。 +fn bridge_enemy_condition_flags(env: &mut Env) { + // ponytail: 允收名单当前仅 Intimidated(唯一有敌方基础条件对消费方的条件); + // 全量 `Condition:*` 泛化会一次性点亮全部敌况词条,留给 parity 点名逐条扩。 + const BRIDGED: &[&str] = &["Intimidated"]; + for cond in BRIDGED { + let flag = ModName::from(format!("Condition:{cond}")); + if env.enemy.mod_db.flag(&env.cfg, flag) { + env.cfg.conditions.insert(format!("Enemy{cond}"), true); + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/pobr-core/src/calc/setup_env.rs b/crates/pobr-core/src/calc/setup_env.rs index 3a93c9df..1cab89ae 100644 --- a/crates/pobr-core/src/calc/setup_env.rs +++ b/crates/pobr-core/src/calc/setup_env.rs @@ -295,6 +295,21 @@ fn inject_enemy_mods(db: &mut ModDb, defaults: &EnemyTierDefaults, tier: EnemyTi if tier.is_pinnacle_or_uber() { push_enemy_condition(db, "PinnacleBoss", "pinnacle_boss"); } + + // 敌人 actor 基础条件态词条(vendor CalcSetup.lua:73-77 initModDB——每个 actor + // 的 modDB 都带的 Intimidated 条件对:受伤 +10% INC / 输出 −10% INC)。条件 var + // 用 cfg 键空间的 `EnemyIntimidated`(config `conditionEnemyIntimidated` 与 + // env_finalize 的敌侧 `Condition:Intimidated` flag 桥接同置此键)。 + // ponytail: 仅落敌方消费的 Intimidated 对;Maimed/Unnerved/Debilitated 等同表 + // 条件在 18-build 语料无来源,parity 点名时再逐条补。 + for (name, value) in [("DamageTaken", 10.0), ("Damage", -10.0)] { + db.add_mod( + Modifier::number(ModName::from(name), ModType::Inc, value) + .with_source("enemy intimidated_base") + .with_origin(enemy_source("intimidated_base")) + .with_tag(ModTag::condition("EnemyIntimidated", false)), + ); + } } /// EHP 进伤 placeholder 注入(M2 F-1):把 vendor ConfigOptions.lua:1982-1996 的 From 8bf4f3daad4267c421ee44516e34d4ab8324e21e Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:38:58 +0800 Subject: [PATCH 60/74] feat(item): The Adorned corrupted-magic-jewel effect scaling What changed - pobr_data::Item gains a corrupted flag, parsed from the item text 'Corrupted' marker line in both item_text parse paths (struct literal sites updated with corrupted: false). - stage_inject_jewels: when a unique jewel carries 'N% increased Effect of Jewel Socket Passive Skills containing Corrupted Magic Jewels' (The Adorned; the text wraps across two physical lines in build XML), every corrupted MAGIC tree-socketed jewel's mods are parsed and value-scaled by 1 + N/100 before injection, using vendor ScaleAddList rounding trunc(round(v * scale, 2)) (ModStore.lua:70-79; CalcSetup.lua:944-948 + :1342-1347). Why - wolf-pack Mana 761.2 vs 770: six corrupted magic 'Rallying Ruby of Valour' jewels (+Int/+Dex/+chaos res enchants) must scale x1.97 (oracle: +5 Int evaluates to 9, +4 Dex to 7). Int 135 -> 139 closes Mana exactly; uncapped chaos resist 72 -> 81 closes the ChaosMaxHit tail. Not modelled (no corpus source): sinister/containJewelSocket socket exemptions, unscalable payloads. Validation - wolf-pack Mana/ManaUnres 770.00 exact, ChaosMaxHit 17008 vs 17007, TotalEHP 129929 vs 130326 (1.00x). --- crates/pobr-build/src/build.rs | 1 + .../src/calc_orchestrator/granted_skills.rs | 1 + .../pobr-build/src/calc_orchestrator/mod.rs | 70 ++++++++++++++++++- .../src/calc_orchestrator/skill_resolve.rs | 1 + crates/pobr-build/src/snapshot.rs | 1 + .../tests/parity/crossbow_reload_golden.rs | 1 + crates/pobr-build/tests/skills/dual_wield.rs | 1 + crates/pobr-build/tests/skills/minions.rs | 1 + .../tests/skills/skill_damage_dps.rs | 1 + crates/pobr-core/src/ingest/item_text.rs | 14 +++- .../tests/sources/env_finalize_flasks.rs | 1 + crates/pobr-core/tests/sources/item_source.rs | 8 +++ crates/pobr-data/src/item.rs | 3 + 13 files changed, 99 insertions(+), 5 deletions(-) diff --git a/crates/pobr-build/src/build.rs b/crates/pobr-build/src/build.rs index e2606fdb..3e31489c 100644 --- a/crates/pobr-build/src/build.rs +++ b/crates/pobr-build/src/build.rs @@ -343,6 +343,7 @@ mod tests { base: ItemBaseId::from("Iron Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec!["+10 to maximum Life".into()], enchant_texts: vec![], diff --git a/crates/pobr-build/src/calc_orchestrator/granted_skills.rs b/crates/pobr-build/src/calc_orchestrator/granted_skills.rs index b829af3b..ad0c2c94 100644 --- a/crates/pobr-build/src/calc_orchestrator/granted_skills.rs +++ b/crates/pobr-build/src/calc_orchestrator/granted_skills.rs @@ -213,6 +213,7 @@ mod tests { base: ItemBaseId::from("Test Wand"), rarity: ItemRarity::Unique, quality: 0, + corrupted: false, implicit_texts: Vec::new(), modifier_texts: vec![format!("Grants Skill: Level {level} Firebolt")], enchant_texts: Vec::new(), diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index cb420cd0..dec9689a 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -1063,6 +1063,7 @@ fn stage_inject_jewels( session: &mut CalculationSession, ctx: &StageCtx<'_>, ) -> Result<(), BuildError> { + let adorned_inc = adorned_corrupted_magic_jewel_inc(&ctx.build.jewels); for jewel in &ctx.build.jewels { let filtered = filter_item_parseable(jewel, engine_ctx(ctx.data)); let texts: Vec<&str> = filtered @@ -1072,13 +1073,70 @@ fn stage_inject_jewels( .chain(&filtered.enchant_texts) .map(String::as_str) .collect(); - session - .add_modifier_texts(texts) - .map_err(|e| BuildError::Parse(e.to_string()))?; + // The Adorned(vendor CalcSetup.lua:944-948 + :1342-1347):树插槽内的 + // **腐化魔法**珠宝全部 mod 按 `1 + N/100` 缩放注入(ScaleAddList 语义, + // 数值 = trunc(round(v×scale, 2)),ModStore.lua:70-79)。 + // ponytail: 不建模 vendor 的 sinister/containJewelSocket 插槽豁免与 + // unscalable 标记(语料无来源),parity 点名时再接。 + if let Some(inc) = adorned_inc + && jewel.rarity == pobr_data::item::ItemRarity::Magic + && jewel.corrupted + { + let scale = 1.0 + inc / 100.0; + let parse_ctx = engine_ctx(ctx.data); + let mut mods: Vec = Vec::new(); + for text in texts { + let Ok(outcome) = parse_ctx.parse(text) else { + continue; + }; + for mut m in outcome.mods { + if let pobr_core::ModValue::Number(v) = m.value { + m.value = pobr_core::ModValue::Number(scale_trunc_2dp(v, scale)); + } + mods.push(m); + } + } + session.add_modifiers(mods); + } else { + session + .add_modifier_texts(texts) + .map_err(|e| BuildError::Parse(e.to_string()))?; + } } Ok(()) } +/// 珠宝列表内 The Adorned 的「N% increased Effect of Jewel Socket Passive Skills +/// containing Corrupted Magic Jewels」数值(XML 内该词条折行为两个物理行,按 +/// 空格拼接后匹配;vendor 解析为 `JewelData{corruptedMagicJewelIncEffect}`)。 +/// 无该珠宝 → `None`。 +fn adorned_corrupted_magic_jewel_inc(jewels: &[Item]) -> Option { + const SUFFIX: &str = + "% increased Effect of Jewel Socket Passive Skills containing Corrupted Magic Jewels"; + for jewel in jewels { + if jewel.rarity != pobr_data::item::ItemRarity::Unique { + continue; + } + let joined = jewel.modifier_texts.join(" "); + if let Some(pos) = joined.find(SUFFIX) { + let head = &joined[..pos]; + let num_start = head + .rfind(|c: char| !c.is_ascii_digit()) + .map_or(0, |i| i + 1); + if let Ok(v) = head[num_start..].parse::() { + return Some(v); + } + } + } + None +} + +/// vendor `ModStore:ScaleAddMod` 数值缩放语义(ModStore.lua:70-79): +/// `m_modf(round(v × scale, 2))` —— 先四舍五入到 2 位小数,再截尾取整。 +fn scale_trunc_2dp(value: f64, scale: f64) -> f64 { + ((value * scale * 100.0).round() / 100.0).trunc() +} + /// 2b'. 范围珠宝 `... Passive Skills in Radius also grant `:按珠宝插槽**半径内 /// 已分配**对应种类节点数 × 授予,展开为全局 modifier text 注入(PoB2 几何口径)。 /// 与装备/天赋路径一致,先 skip-and-collect 过滤硬失败词条,避免单条中止整批。 @@ -2050,6 +2108,7 @@ mod ring3_tests { base: ItemBaseId::from("Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec!["+30 to maximum Life".into()], enchant_texts: vec![], @@ -2181,6 +2240,7 @@ mod tests { base: ItemBaseId::from("Iron Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![format!("+{amount} to maximum Life")], enchant_texts: vec![], @@ -2200,6 +2260,7 @@ mod tests { base: ItemBaseId::from("Solar Amulet"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![], enchant_texts: vec!["Allocates Paragon".into()], @@ -3248,6 +3309,7 @@ mod tests { base: ItemBaseId::from("Visceral Quiver"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec!["53% increased Damage with Bow Skills".into()], enchant_texts: vec![], @@ -4452,6 +4514,7 @@ mod tests { base: ItemBaseId::from(base_name.as_str()), rarity: ItemRarity::Normal, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![], enchant_texts: vec![], @@ -4480,6 +4543,7 @@ mod tests { base: ItemBaseId::from(base_name.as_str()), rarity: ItemRarity::Normal, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![], enchant_texts: vec![], diff --git a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs index f6c356e3..38ce5ee9 100644 --- a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs +++ b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs @@ -936,6 +936,7 @@ mod kalandra_tests { base: ItemBaseId::from("Ring"), rarity: ItemRarity::Unique, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: texts.iter().map(|s| s.to_string()).collect(), enchant_texts: vec![], diff --git a/crates/pobr-build/src/snapshot.rs b/crates/pobr-build/src/snapshot.rs index dd5ff078..41602142 100644 --- a/crates/pobr-build/src/snapshot.rs +++ b/crates/pobr-build/src/snapshot.rs @@ -248,6 +248,7 @@ mod tests { base: ItemBaseId::from("Iron Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec!["+10 to maximum Life".into()], enchant_texts: vec![], diff --git a/crates/pobr-build/tests/parity/crossbow_reload_golden.rs b/crates/pobr-build/tests/parity/crossbow_reload_golden.rs index 2b1193b0..988529dd 100644 --- a/crates/pobr-build/tests/parity/crossbow_reload_golden.rs +++ b/crates/pobr-build/tests/parity/crossbow_reload_golden.rs @@ -34,6 +34,7 @@ fn bare_weapon(base_name: &str) -> Item { base: ItemBaseId::from(base_name), rarity: ItemRarity::Normal, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![], enchant_texts: vec![], diff --git a/crates/pobr-build/tests/skills/dual_wield.rs b/crates/pobr-build/tests/skills/dual_wield.rs index 5827b01c..7b44fd59 100644 --- a/crates/pobr-build/tests/skills/dual_wield.rs +++ b/crates/pobr-build/tests/skills/dual_wield.rs @@ -31,6 +31,7 @@ fn weapon(base_name: &str, mods: &[&str]) -> Item { base: ItemBaseId::from(base_name), rarity: ItemRarity::Normal, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: mods.iter().map(|s| s.to_string()).collect(), enchant_texts: vec![], diff --git a/crates/pobr-build/tests/skills/minions.rs b/crates/pobr-build/tests/skills/minions.rs index 71eb3339..ee48ddf2 100644 --- a/crates/pobr-build/tests/skills/minions.rs +++ b/crates/pobr-build/tests/skills/minions.rs @@ -181,6 +181,7 @@ fn minion_increased_damage_raises_minion_dps() { base: ItemBaseId::from("Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec!["Minions deal 50% increased Damage".into()], enchant_texts: vec![], diff --git a/crates/pobr-build/tests/skills/skill_damage_dps.rs b/crates/pobr-build/tests/skills/skill_damage_dps.rs index 02539081..56a8be5a 100644 --- a/crates/pobr-build/tests/skills/skill_damage_dps.rs +++ b/crates/pobr-build/tests/skills/skill_damage_dps.rs @@ -151,6 +151,7 @@ fn bare_weapon(base_name: &str) -> Item { base: ItemBaseId::from(base_name), rarity: ItemRarity::Normal, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![], enchant_texts: vec![], diff --git a/crates/pobr-core/src/ingest/item_text.rs b/crates/pobr-core/src/ingest/item_text.rs index b6f66589..d2eed987 100644 --- a/crates/pobr-core/src/ingest/item_text.rs +++ b/crates/pobr-core/src/ingest/item_text.rs @@ -108,6 +108,7 @@ pub fn parse_item_text(raw: &str) -> Result { let mut rolled_defence = RolledDefence::default(); // 后续段:先扫描元数据(Quality / Implicits 头 / 防御底值),再按行归类词条。 + let mut corrupted = false; for section in §ions[1..] { let mut mod_lines: Vec<&str> = Vec::new(); @@ -119,7 +120,10 @@ pub fn parse_item_text(raw: &str) -> Result { } else if accumulate_rolled_defence(line, &mut rolled_defence) { // 已掷出防御底值行:记入 rolled_defence,不计入词条。 } else if is_metadata_line(line) { - // 元数据行不计入词条。 + // 元数据行不计入词条;`Corrupted` 标记行落 corrupted 状态。 + if line.trim() == "Corrupted" { + corrupted = true; + } } else { mod_lines.push(line); } @@ -138,6 +142,7 @@ pub fn parse_item_text(raw: &str) -> Result { base, rarity, quality, + corrupted, implicit_texts, modifier_texts, enchant_texts, @@ -203,6 +208,7 @@ pub fn parse_pob_xml_item(raw: &str) -> Result { let mut quality = 0u8; let mut implicit_count = 0usize; let mut rolled_defence = RolledDefence::default(); + let mut corrupted = false; let mut mod_lines: Vec<&str> = Vec::new(); for &line in &lines[idx..] { if let Some(value) = quality_from_line(line) { @@ -212,7 +218,10 @@ pub fn parse_pob_xml_item(raw: &str) -> Result { } else if accumulate_rolled_defence(line, &mut rolled_defence) { // 已掷出防御底值行:记入 rolled_defence,不计入词条。 } else if is_xml_metadata_line(line) { - // 元数据行不计入词条。 + // 元数据行不计入词条;`Corrupted` 标记行落 corrupted 状态。 + if line == "Corrupted" { + corrupted = true; + } } else { mod_lines.push(line); } @@ -233,6 +242,7 @@ pub fn parse_pob_xml_item(raw: &str) -> Result { base, rarity, quality, + corrupted, implicit_texts, modifier_texts, enchant_texts, diff --git a/crates/pobr-core/tests/sources/env_finalize_flasks.rs b/crates/pobr-core/tests/sources/env_finalize_flasks.rs index 1fc5098a..2c0e7073 100644 --- a/crates/pobr-core/tests/sources/env_finalize_flasks.rs +++ b/crates/pobr-core/tests/sources/env_finalize_flasks.rs @@ -25,6 +25,7 @@ fn utility_item(base: &str, explicits: &[&str]) -> Item { base: ItemBaseId::from(base), rarity: ItemRarity::Magic, quality: 0, + corrupted: false, implicit_texts: Vec::new(), modifier_texts: explicits.iter().map(|t| (*t).to_string()).collect(), enchant_texts: Vec::new(), diff --git a/crates/pobr-core/tests/sources/item_source.rs b/crates/pobr-core/tests/sources/item_source.rs index 147fd85b..c480df39 100644 --- a/crates/pobr-core/tests/sources/item_source.rs +++ b/crates/pobr-core/tests/sources/item_source.rs @@ -16,6 +16,7 @@ fn helmet(texts: &[&str]) -> Item { base: ItemBaseId::from("Helmet"), rarity: ItemRarity::Rare, quality: 20, + corrupted: false, implicit_texts: Vec::new(), modifier_texts: texts.iter().map(|t| (*t).to_string()).collect(), enchant_texts: Vec::new(), @@ -114,6 +115,7 @@ fn ingest_item_distinguishes_implicit_explicit_enchant_sections() { base: ItemBaseId::from("Helmet"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec!["+30% to Fire Resistance".to_string()], modifier_texts: vec!["+40 to maximum Life".to_string()], enchant_texts: vec!["+25% to Cold Resistance".to_string()], @@ -157,6 +159,7 @@ fn ingest_item_collects_unsupported_texts_across_sections() { base: ItemBaseId::from("Helmet"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec!["split".to_string()], modifier_texts: vec!["+40 to maximum Life".to_string(), "mirrored".to_string()], enchant_texts: Vec::new(), @@ -179,6 +182,7 @@ fn ingested_item_modifiers_attribute_back_to_slot() { base: ItemBaseId::from("Iron Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: Vec::new(), modifier_texts: vec!["+25 to maximum Life".to_string()], enchant_texts: Vec::new(), @@ -209,6 +213,7 @@ fn ingested_implicit_attributes_to_item_implicit_source() { base: ItemBaseId::from("Amber Amulet"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec!["+25 to maximum Life".to_string()], modifier_texts: Vec::new(), enchant_texts: Vec::new(), @@ -242,6 +247,7 @@ fn session_add_item_feeds_minimal_calc() { base: ItemBaseId::from("Plate Vest"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: Vec::new(), modifier_texts: vec![ "+40 to maximum Life".to_string(), @@ -294,6 +300,7 @@ fn bare_item(base: &str, slot_quality: u8) -> Item { base: ItemBaseId::from(base), rarity: ItemRarity::Rare, quality: slot_quality, + corrupted: false, implicit_texts: Vec::new(), modifier_texts: Vec::new(), enchant_texts: Vec::new(), @@ -368,6 +375,7 @@ fn utility_item(base: &str, implicits: &[&str], explicits: &[&str]) -> Item { base: ItemBaseId::from(base), rarity: ItemRarity::Magic, quality: 0, + corrupted: false, implicit_texts: implicits.iter().map(|t| (*t).to_string()).collect(), modifier_texts: explicits.iter().map(|t| (*t).to_string()).collect(), enchant_texts: Vec::new(), diff --git a/crates/pobr-data/src/item.rs b/crates/pobr-data/src/item.rs index 0af8ff09..f501fa26 100644 --- a/crates/pobr-data/src/item.rs +++ b/crates/pobr-data/src/item.rs @@ -57,6 +57,9 @@ pub struct Item { pub base: ItemBaseId, pub rarity: ItemRarity, pub quality: u8, + /// 是否已腐化(item 文本的 `Corrupted` 标记行)。消费方:The Adorned 珠宝 + /// 效果缩放(corrupted magic jewel 门控)等 vendor `item.corrupted` 语义。 + pub corrupted: bool, /// implicit 词条文本(基底固有词缀),归因到 `SourceKind::ItemImplicit`。 pub implicit_texts: Vec, /// explicit 词条文本(前后缀),归因到 `SourceKind::ItemAffix`。 From 120f0f556cfa7078f909fc9d76a4b62965c3363a Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:39:12 +0800 Subject: [PATCH 61/74] fix(parity): recompute item armour/evasion from base DB like ES What changed - item_rolled_defence: when the base item is known, all three defence components (armour/evasion/energy shield) are recomputed as round((base + localFlat) * (1 + localInc/100) * (1 + quality/100)) (Item.lua:1994-1996), instead of ES only. Display-line fallback remains for unknown bases. Why - titan Armour 0.985x: three 0.5.4b-buffed Runeforged bases whose item-text 'Armour:' lines lag the base DB (gloves 96 vs 101, helmet 192 vs 284, boots 58 vs 100; oracle ArmourOn pins each; helmet recompute 169*1.40*1.20 = 284 exact). Same family as the earlier ES-only fix (c705fe4). Note the #10 'shield 1736 vs 1725' hypothesis was wrong - oracle armourData.Armour is exactly 1736. Validation - titan Armour 39545.68 vs 39546 (0.99x -> 1.00x), ES/ESRecoveryCap 55 exact, Phys/Fire/Cold/Light/Chaos MaxHit exact. Per-item round() also lands pathfinder Evasion 0.98x -> 1.00x and twister DeflectChance 0.97x -> 1.00x exact. No cell regresses fleet-wide. --- .../pobr-build/src/calc_orchestrator/defence.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/defence.rs b/crates/pobr-build/src/calc_orchestrator/defence.rs index 5c53d624..32001ad3 100644 --- a/crates/pobr-build/src/calc_orchestrator/defence.rs +++ b/crates/pobr-build/src/calc_orchestrator/defence.rs @@ -228,14 +228,15 @@ pub(crate) fn item_rolled_defence(item: &Item, data: &BuildData, level: u32) -> } else { base * (1.0 + local_pct[idx] / 100.0) * (1.0 + quality_pct / 100.0) }; - // ES(idx 2):PoB2 恒从基底物品 DB 重算 `EnergyShield = round((esBase+flat) × - // (1+localInc/100) × (1+quality/100))`(Item.lua:1994-1996),不信物品文本的 - // `Energy Shield: N` 展示行——该行可能滞后于当前数据版本的基底 ES(跨版本重算 - // 与导入期展示值分歧,titan 手套 26→28 / 靴 15→27 = 41→55 根因)。基底已知时 - // ES 优先重算;护甲/闪避仍信 rolled 行(其局部 inc 解析不保证在所有基底上完整, - // 避免回归)。 - out[idx] = if idx == 2 && default_val.is_some() { - recompute + // PoB2 恒从基底物品 DB 重算三防 `round((base+flat) × (1+localInc/100) × + // (1+quality/100))`(Item.lua:1994-1996),不信物品文本的 `Armour:/Evasion:/ + // Energy Shield: N` 展示行——该行可能滞后于当前数据版本的基底值(跨版本重算 + // 与导入期展示值分歧:titan ES 手套 26→28 / 靴 15→27 = 41→55;0.5.4b + // Runeforged 基底护甲 buff 后 titan 手套 96→101 / 盔 192→284 / 靴 58→100, + // Gear:Armour 6100→6239 = vendor)。基底已知时重算(vendor round 同口径); + // 基底不在库时回退 rolled 行(不臆造)。 + out[idx] = if default_val.is_some() { + recompute.round() } else { rolled_val.unwrap_or(recompute) }; From 8c7b39b84c81a4942e3c9d8099a1197a8c63732f Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:39:22 +0800 Subject: [PATCH 62/74] chore(oracle): enemy dealt-damage tabulate + ORACLE_DUMP_ITEMS item dump - enemyMitigation.dealtMods: per-mod source dump of enemy outgoing-Damage scalers (INC/MORE over Damage/Damage/ElementalDamage) - the feeds of EnemyDamageMult (Enfeeble / Intimidated-style). - ORACLE_DUMP_ITEMS="2,17": dumps listed build items' parsed modLines, evaluated modList, quality, armourData and base armour - provenance diffs for jewel-effect scaling and item defence recompute. Zero cost when unset. --- tools/pob2-oracle/oracle.lua | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tools/pob2-oracle/oracle.lua b/tools/pob2-oracle/oracle.lua index ad60abc2..3cc5e345 100644 --- a/tools/pob2-oracle/oracle.lua +++ b/tools/pob2-oracle/oracle.lua @@ -579,6 +579,26 @@ if enemyDB then end end enemyMitigation.resistMods = resistMods + -- Per-mod source dump of enemy outgoing-Damage scalers (Enfeeble-style + -- "enemies deal less damage" curses feeding EnemyDamageMult). + local dealtMods = {} + for _, mt in ipairs({ "INC", "MORE" }) do + local okDmg, dmgTable = pcall(function() + return enemyDB:Tabulate(mt, nil, "Damage", "PhysicalDamage", "FireDamage", + "ColdDamage", "LightningDamage", "ChaosDamage", "ElementalDamage") + end) + if okDmg and dmgTable then + for _, entry in ipairs(dmgTable) do + dealtMods[#dealtMods + 1] = { + modType = mt, + name = entry.mod.name, + value = entry.value, + source = entry.mod.source, + } + end + end + end + enemyMitigation.dealtMods = dealtMods -- Exposure effect composition (vendor CalcPerform.lua:3222-3227): global modDB -- INC + per-active-skill skill-scoped INC, plus extra exposure BASE. local pdb = mainEnv.player.modDB @@ -1008,10 +1028,46 @@ do end end +-- Ad-hoc item mod dump: ORACLE_DUMP_ITEMS="18,19,21" dumps each listed build +-- item's parsed modLines + evaluated modList (post-range) for provenance diffs. +local itemDump +do + local ids = os.getenv("ORACLE_DUMP_ITEMS") + if ids and #ids > 0 then + itemDump = {} + for idStr in ids:gmatch("[^,%s]+") do + local id = tonumber(idStr) + local item = build.itemsTab.items[id] + if item then + local lines = {} + for _, ml in ipairs(item.explicitModLines or {}) do + lines[#lines + 1] = { line = ml.line, range = ml.range } + end + for _, ml in ipairs(item.enchantModLines or {}) do + lines[#lines + 1] = { line = ml.line, range = ml.range, enchant = true } + end + for _, ml in ipairs(item.implicitModLines or {}) do + lines[#lines + 1] = { line = ml.line, range = ml.range, implicit = true } + end + local mods = {} + if item.modList then + for _, m in ipairs(item.modList) do + mods[#mods + 1] = { name = m.name, type = m.type, value = type(m.value) ~= "table" and m.value or nil } + end + end + itemDump[tostring(id)] = { title = item.title, baseName = item.baseName, lines = lines, mods = mods, + armourData = item.armourData, quality = item.quality, + baseArmour = item.base and item.base.armour or nil } + end + end + end +end + ---------------------------------------------------------------------- -- Assemble report ---------------------------------------------------------------------- local report = { + itemDump = itemDump, spiritReservations = spiritReservations, spiritReservedBreakdown = spiritReservedBreakdown, defenceModList = defenceModList, From af1c9a30219931592955aa3940b4f48c6956610d Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:39:22 +0800 Subject: [PATCH 63/74] test(parity): ratchet defensive baselines for #13 residual fixes - def 25-col: 431 -> 437 @5%, 439 -> 445 @10% (six wolf-pack cells: 5x MaxHit + TotalEHP). off/dot/core-8 unchanged. - docs/adapting-to-0.5.4b.md: #13 section - three root causes (enemy Intimidated base pair, The Adorned corrupted-magic-jewel scaling, Runeforged base-armour display-line lag) with oracle evidence and vendor line refs; oracle tooling notes. --- .../pobr-build/tests/parity/ninja_parity.rs | 24 +++++++- docs/adapting-to-0.5.4b.md | 55 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index dc1fd8f4..896db68c 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -656,8 +656,28 @@ const BASELINE_DEF_CORE_HIT5: usize = 142; // 存量 #7-3/4(charm-guard 摘除 // 0.72x→0.80x / TotalEHP 0.74x→0.83x(池侧 3817 vs oracle 3826.67 = 0.9975 已 // 闭合,余量 = per-type taken 乘子侧 ~10% 均匀缺口 + Mana 761.2 vs 770,均与 // companion 层无关)。 -const BASELINE_DEF_HIT5: usize = 431; // #12 companion 后 431/450(存量 #7-3/4 425:ritualist 6 格 + gemling 9 格 + wolf-pack Life/Armour 精确翻正,wolf-pack MaxHit 族随 charm-guard 掩蔽摘除移出;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) -const BASELINE_DEF_HIT10: usize = 439; // #12 companion 后 439/450(Communion+Voices 434;Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) +// **#13 防御残差定点修复重记(+6 @5% 431→437 / +6 @10% 439→445)**:三根因—— +// ① wolf-pack per-type taken 乘子 ~10% 均匀缺口 = 敌人 Intimidated 基础条件对 +// (vendor CalcSetup.lua:73-77 `Damage INC -10 / DamageTaken INC 10 if +// Intimidated`)未建:体甲词条「Enemies in your Presence are Intimidated」的 +// 敌侧 `Condition:Intimidated` flag 已入 enemy db 但无消费方。setup_enemy 注入 +// 条件对 + env_finalize 桥接 flag→cfg `EnemyIntimidated` + orchestrator 默认 +// 置真 `EnemyInPresence`(vendor CalcPerform.lua:524)→ `EnemyDamageMult` +// 0.9 生效(max hit 末端除数 :3734-3771 + EHP 进伤),wolf-pack 5×MaxHit +// 0.80-0.90x→1.00x、TotalEHP 0.83x→1.00x、PhysDR 68.03 精确。 +// ② wolf-pack Mana 761.2 vs 770 = The Adorned「97% increased Effect of Jewel +// Socket Passive Skills containing Corrupted Magic Jewels」未建:腐化魔法珠宝 +// (Rallying Ruby ×6,enchant +Int/+Dex/+chaos res)mod 未按 1.97 缩放 +// (vendor CalcSetup.lua:944-948/:1342-1347,ScaleAddList = trunc(round(v×s,2)))。 +// orchestrator stage_inject_jewels 解析后缩放注入 → Int 135→139 → Mana 770 +// 精确(连带 ChaosMaxHit 尾差闭合)。 +// ③ titan Armour 0.985x = 三件 Runeforged 基底(0.5.4b buff 过)护甲展示行滞后: +// item_rolled_defence 的「基底已知恒重算」从 ES-only 扩到三防(vendor +// Item.lua:1994-1996 + round 口径)——手套 96→101 / 盔 192→284 / 靴 58→100, +// Gear:Armour 6100→6239 = vendor,titan Armour/ES/5×MaxHit 精确闭合;连带 +// pathfinder Evasion 0.98x→1.00x、twister DeflectChance 0.97x→1.00x 精确化。 +const BASELINE_DEF_HIT5: usize = 437; // #13 残差定点后 437/450(#12 companion 431;存量 #7-3/4 425:ritualist 6 格 + gemling 9 格 + wolf-pack Life/Armour 精确翻正,wolf-pack MaxHit 族随 charm-guard 掩蔽摘除移出;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +const BASELINE_DEF_HIT10: usize = 445; // #13 残差定点后 445/450(#12 companion 439;Communion+Voices 434;Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 // ReservationPlayer、buff 侧 BannerPlayer(Aura)在附加位)在 buff_skill_specs diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 5de25544..e73f51d3 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -517,9 +517,64 @@ not the companion layer. Baselines: def 425→431 @5%, 434→439 @10%; off/dot/ core-8 unchanged; 18-build per-cell diff shows only twister and wolf-pack moving. +**#13 — defensive residuals pinpoint fixes. ✅ DONE (wolf-pack + titan defensive +columns all exact).** Three independent root causes, each pinned by oracle: + +- *wolf-pack per-type taken ~10% uniform gap = enemy Intimidated base pair.* + Oracle `EnemyDamageMult = 0.9` for all five types; enemyDB tabulate shows + a single `Damage INC -10, source "Base"` — the actor-init conditional mod + `Damage INC -10 { Condition: Intimidated }` (CalcSetup.lua:73-77, paired with + `DamageTaken INC 10`), lit by the body-armour corrupted implicit "Enemies in + your Presence are Intimidated" (flag reaches PoBR's enemy db but had no + consumer). Note the Enfeeble curse payload is *not* the source — its MORE is + gated `!Unique` and the Pinnacle enemy is Unique on both sides. Fix (all + generic consumers): `setup_enemy` injects the Intimidated base pair (tag + `EnemyIntimidated`); `env_finalize::bridge_enemy_condition_flags` bridges the + enemy-db `Condition:Intimidated` FLAG into cfg `EnemyIntimidated` (vendor + ModStore GetCondition semantics; allow-list = Intimidated only); orchestrator + seeds `EnemyInPresence` true (CalcPerform.lua:524 default — PoBR does not + model PresenceRadius vs enemyDistance). `EnemyDamageMult` was already wired + as the max-hit end divisor (:3734-3771) and EHP damage-in multiplier — only + the mod was missing. wolf-pack Phys/Fire/Cold/Light MaxHit 0.90x→1.00x, + PhysDR 65.69→68.03 (oracle 68), TotalEHP 0.83x→(with the Mana fix) 1.00x. +- *wolf-pack Mana 761.2 vs 770 = The Adorned corrupted-magic-jewel scaling.* + Oracle Int=139 vs PoBR 135: the six tree-socketed "Rallying Ruby of Valour" + magic jewels (corrupted, enchant implicits +Int/+Dex/+chaos res) are scaled + ×1.97 by The Adorned's "97% increased Effect of Jewel Socket Passive Skills + containing Corrupted Magic Jewels" (CalcSetup.lua:944-948 sets the + multiplier, :1342-1347 `ScaleAddList`, value = `trunc(round(v×scale, 2))`, + ModStore.lua:70-79): +5 Int→9, +4 Dex→7. Fix: `Item.corrupted` field (item + text `Corrupted` marker line, both parse paths) + + `stage_inject_jewels` parses corrupted-magic-jewel texts and injects + value-scaled modifiers (`adorned_corrupted_magic_jewel_inc` matches the + two-physical-line wrapped text). Int 135→139 → Mana 770 exact; chaos resist + uncapped 72→81 (capped 75) closes the ChaosMaxHit tail (17008 vs 17007). + Not modelled: sinister/containJewelSocket socket exemptions, `unscalable` + payloads (no corpus source). +- *titan Armour 0.985x = Runeforged base-armour display-line lag* (the #10-era + "shield 1736 vs recomputed 1725" hypothesis was wrong — oracle + `item.armourData.Armour = 1736` exactly, the shield was never the gap). + Per-slot dump vs oracle `ArmourOn`: gloves 96 vs 101, helmet 192 vs + 284, boots 58 vs 100 — three 0.5.4b-buffed Runeforged bases whose item-text + `Armour:` lines lag the base DB (same family as the c705fe4 ES fix; helmet + recompute 169×1.40×1.20=283.9→284 matches oracle exactly). Fix: + `item_rolled_defence` now recomputes **all three** defences from the base DB + when the base is known (vendor Item.lua:1994-1996, incl. `round()`), not + just ES. titan Armour 38960→39546 exact, ES 54.67→55 exact, 5×MaxHit exact; + fleet-wide the per-item `round()` also lands pathfinder Evasion + 0.98x→1.00x and twister DeflectChance 0.97x→1.00x exact. + +Baselines: def 431→437 @5%, 439→445 @10% (the six wolf-pack cells); +off/dot/core-8 unchanged; 18-build per-cell diff shows no cell regressing. +titan TotalEHP 0.92x remains (separate residual, outside #13 scope). + ## Tooling - `examples/demo-bd-test/tools/recapture_golden.py` — refresh fixture goldens against the currently vendored PoB2 (run after any vendor bump). - `tools/pob2-oracle/run.sh out.json` — per-build 0.5.4b breakdown (`mainOutput` scalars + `intermediates` / `components` / `conversionTable`). + `enemyMitigation.dealtMods` lists enemy outgoing-Damage scalers (Enfeeble / + Intimidated-style feeds of `EnemyDamageMult`); `ORACLE_DUMP_ITEMS="2,17"` + dumps listed build items' parsed modLines + evaluated modList + armourData + (provenance diffs for jewel-effect scaling / item defence recompute). From 2d6f1690db7b1f571e351edb3066a0b114546939 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:44:54 +0800 Subject: [PATCH 64/74] test: pin minion life to the ally table semantics from #12 The #12 companion work switched summon life to monsterAllyLifeTable with vendor floor semantics (CalcPerform.lua:1046); two minion tests still pinned the enemy monsterLifeTable expectation and slipped through the merge gate (the pobr-core run output was truncated by the tee log). --- crates/pobr-core/tests/sources/minion.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/pobr-core/tests/sources/minion.rs b/crates/pobr-core/tests/sources/minion.rs index 985aa6cb..53eddb5f 100644 --- a/crates/pobr-core/tests/sources/minion.rs +++ b/crates/pobr-core/tests/sources/minion.rs @@ -33,14 +33,16 @@ fn gem_level_table_maps_per_pob2() { } #[test] -fn base_stats_use_monster_table_times_normalizer() { +fn base_stats_use_ally_life_table_times_normalizer() { + // #12 起召唤物生命走盟友表 + floor(vendor CalcPerform.lua:1046 + // `m_floor(monsterAllyLifeTable[level] × minionData.life)`),不再用敌方 + // monsterLifeTable。armour/evasion 仍走敌方表(vendor 同源)。 let data = MinionData { life: 0.7, ..MinionData::default() }; let base = derive_minion_base_stats(20, &data); - let row = MonsterScalingRow::at_level(40); - let expected = (row.life as f64 * 0.7 * 1_000_000_000.0).round() / 1_000_000_000.0; + let expected = (pobr_data::monster::monster_ally_life(40) as f64 * 0.7).floor(); assert!((base.life - expected).abs() < 1e-6); assert!(base.life > 0.0); } @@ -282,8 +284,8 @@ fn build_context_from_raging_spirit_life_matches_pob2() { // 暴怒魂灵:life=0.25,gem_level=20 → 怪物等级 40。 let def = minion_def_raging_spirit(); let ctx = build_minion_context_from_def(&def, 20, vec![], vec![], AttributeInfusion::default()); - let row = MonsterScalingRow::at_level(40); - let expected = (row.life as f64 * 0.25 * 1e9).round() / 1e9; + // #12:生命走盟友表 + floor(vendor CalcPerform.lua:1046)。 + let expected = (pobr_data::monster::monster_ally_life(40) as f64 * 0.25).floor(); assert!( (ctx.base.life - expected).abs() < 1e-6, "RagingSpirit life: expected {expected}, got {}", From 22e1b79fbc461304aa70be39f1202130715e9ec6 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:46:07 +0800 Subject: [PATCH 65/74] feat(defence): Smith connected-notable multiplier + item attribute-requirement snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two provisioning gaps left Masterwork's armour mods evaluating to zero on warrior-smith-of-kitava-shield-wall (both mods parsed fine, their inputs were never provided): 1. Multiplier:AllocatedConnectedNotable — vendor counts allocated notables flagged applyToArmour=true in tree.lua (PassiveSpec.lua:1753, CalcSetup.lua:840-841). The flag never existed in PoBR tree data: it is now transcribed by the --tree-coords backfill (PassiveNodeDef.apply_to_armour, serde-default) and counted in inject_per_x_multipliers. Smith allocates 8 of the 12 flagged notables -> +200x8 = 1600 flat Armour. 2. StrRequirementsOn — vendor snapshots per-slot item attribute requirements (CalcPerform.lua:1848-1857) for 'Gain Armour equal to 150% of total Strength Requirements of Equipped Boots, Gloves and Helmet' (PercentStat). Base-item requirements were absent from PoBR data (.dat bundle unavailable) — extended the extract-bases vendor channel with req = { str/dex/int } (BaseItemDef.req_str/dex/int via overlay merge) and provision RequirementsOn cfg.stats per equipped item. reqMult (GlobalAttributeRequirements) held at 1 until a fixture needs it. smith Armour 37386 -> 49096.16 vs golden 49096 (0.76x -> 1.00x exact); PhysMaxHit/Fire/Cold/LightMaxHit all land exact (0.91-0.93x -> 1.00x), ChaosMaxHit 16124 vs 16123. Def 25-col 437 -> 442 @5% / 442 -> 444 @10%, core-8 143 -> 144/144 (100%). --- .../src/calc_orchestrator/granted_skills.rs | 3 + .../pobr-build/src/calc_orchestrator/mod.rs | 56 + crates/pobr-core/src/calc/ehp.rs | 5 +- .../src/catalog/base_item_overrides.rs | 8 + crates/pobr-data/src/catalog/items.rs | 12 + crates/pobr-data/src/catalog/tree.rs | 6 + .../src/domains/base_item_overrides.rs | 40 + crates/pobr-tree/tests/radius_jewel.rs | 1 + crates/pobr-tree/tests/tree.rs | 2 + data/4.5.4.3/base/passive_tree.json | 36 +- data/4.5.4.3/overlay/base_item_overrides.json | 5413 ++++++++++++----- tools/pobr-data-adapter/src/main.rs | 3 + tools/pobr-data-adapter/src/tree.rs | 1 + tools/pobr-data-adapter/src/tree_anoints.rs | 1 + tools/pobr-data-adapter/src/tree_coords.rs | 10 + tools/pobr-data-adapter/src/tree_versions.rs | 1 + .../src/extract_base_overrides.lua | 19 +- tools/sync-pob-catalog/src/extract_bases.rs | 12 + 18 files changed, 4065 insertions(+), 1564 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/granted_skills.rs b/crates/pobr-build/src/calc_orchestrator/granted_skills.rs index b829af3b..350c8068 100644 --- a/crates/pobr-build/src/calc_orchestrator/granted_skills.rs +++ b/crates/pobr-build/src/calc_orchestrator/granted_skills.rs @@ -180,6 +180,9 @@ mod tests { data.base_items.insert( "Firebolt".to_string(), BaseItemDef { + req_str: 0, + req_dex: 0, + req_int: 0, id: GEM_ID.to_string(), name: "Firebolt".to_string(), item_class: "Skill Gem".to_string(), diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index 76cd17cd..2622e4b7 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -1590,6 +1590,55 @@ fn inject_per_x_multipliers(session: &mut CalculationSession, build: &Build, dat session.set_multiplier("StrengthMoteSkillCount", str_mote); session.set_multiplier("DexterityMoteSkillCount", dex_mote); session.set_multiplier("IntelligenceMoteSkillCount", int_mote); + // Smith of Kitava 身甲连接 notable 计数(vendor CalcSetup.lua:840-841: + // 已分配且 tree.lua `applyToArmour=true` 的 notable 数 → + // `Multiplier:AllocatedConnectedNotable`)。消费方 = Masterwork + // 『+200 to Armour for each Connected Notable Passive Skill Allocated』。 + let connected_notables = build + .tree + .allocated_nodes + .iter() + .filter(|id| { + data.passive_nodes + .get(&id.0) + .is_some_and(|n| n.apply_to_armour) + }) + .count(); + if connected_notables > 0 { + session.set_multiplier("AllocatedConnectedNotable", connected_notables as f64); + } + // 装备属性需求快照(vendor CalcPerform.lua:1848-1857: + // `output[attr.."RequirementsOn"..slot] = floor(itemReq × reqMult)`)—— + // 『Gain Armour equal to N% of total Strength Requirements of Equipped + // Boots, Gloves and Helmet』(PercentStat `StrRequirementsOn`)取数源。 + // ponytail: reqMult(GlobalAttributeRequirements 词条族)恒按 1;出现带 + // 「reduced attribute requirements」的相关 build 时再接乘子。 + for (var, value) in per_slot_attribute_requirements(build, data) { + session.set_stat(var, value); + } +} + +/// 每槽位装备的属性需求(`{Str,Dex,Int}RequirementsOn` → 值)。 +/// 槽位词根与 PercentStat tag 的 stat 名对齐(`StrRequirementsOnboots` 等, +/// 小写槽名 = 引擎解析产物);无需求/空槽不产出。 +fn per_slot_attribute_requirements(build: &Build, data: &BuildData) -> Vec<(String, f64)> { + let mut out = Vec::new(); + for (slot, item) in build.equipped_items() { + let Some(def) = data.base_items.get(&item.base.to_string()) else { + continue; + }; + let slot_key = slot.id(); + for (attr, req) in [ + ("Str", def.req_str), + ("Dex", def.req_dex), + ("Int", def.req_int), + ] { + if req > 0 { + out.push((format!("{attr}RequirementsOn{slot_key}"), f64::from(req))); + } + } + } + out } /// Attribute-Mote 计数(Gemling Virtuous Barrier):base 3/3/3 + 每个启用非辅助 @@ -2053,6 +2102,7 @@ mod ring3_tests { fn ring_slot_data() -> BuildData { // 『+1 Ring Slot』词条节点(Ritualist『Unfurled Finger』形态)。 let node = pobr_data::catalog::PassiveNodeDef { + apply_to_armour: false, skill: 34785, id: "ascendancy_ritualist_unfurled_finger".into(), name: Some("Unfurled Finger".into()), @@ -2347,6 +2397,7 @@ mod tests { fn passive_node_contributes_attributed_life() { // 构造一个携带 +30 maximum Life 的普通节点,分配后应抬升生命。 let node = pobr_data::catalog::PassiveNodeDef { + apply_to_armour: false, skill: 12345, id: "test_life_node".into(), name: Some("Life Node".into()), @@ -2394,6 +2445,7 @@ mod tests { stats: Vec, ) -> pobr_data::catalog::PassiveNodeDef { pobr_data::catalog::PassiveNodeDef { + apply_to_armour: false, skill, id: format!("n{skill}"), name: None, @@ -3211,6 +3263,7 @@ mod tests { fn slot_bonus_effect_scales_covers_equipped_quiver() { use pobr_data::passive_tree::{NodeId, PassiveTreeSpec}; let quiver_node = pobr_data::catalog::PassiveNodeDef { + apply_to_armour: false, skill: 30341, id: "bow_quiver_effect".into(), name: Some("Master Fletching".into()), @@ -4391,6 +4444,9 @@ mod tests { /// 测试用武器基底(仅 item_class 参与持握/近战判定)。 fn weapon_base_item(name: &str, item_class: &str) -> pobr_data::catalog::BaseItemDef { pobr_data::catalog::BaseItemDef { + req_str: 0, + req_dex: 0, + req_int: 0, id: format!("Test/{name}"), name: name.to_string(), item_class: item_class.to_string(), diff --git a/crates/pobr-core/src/calc/ehp.rs b/crates/pobr-core/src/calc/ehp.rs index f55ddc09..349fe1bd 100644 --- a/crates/pobr-core/src/calc/ehp.rs +++ b/crates/pobr-core/src/calc/ehp.rs @@ -581,8 +581,9 @@ pub fn taken_hit_per_type( // 注意这是**取整**变体(Common.lua round=floor(x+0.5);armourReductionF 才是 // 小数变体,仅 takenHitFromDamage/:437 用)。golden PhysicalDamageReduction // 恒为整数即由此来。 - let armour_dr = vendor_round(armour_reduction(mit.effective_applied_armour[i], damage) * 100.0) - .min(mit.dr_max_pct[i]); + let armour_dr = + vendor_round(armour_reduction(mit.effective_applied_armour[i], damage) * 100.0) + .min(mit.dr_max_pct[i]); // :2382 totalReduct = min(drMax, armourReduct + flatDR)。 let total_dr = (armour_dr + mit.flat_dr_pct[i]).min(mit.dr_max_pct[i]); // :2383 reductMult = 1 − clamp(totalReduct − overwhelm, 0, drMax)/100。 diff --git a/crates/pobr-data/src/catalog/base_item_overrides.rs b/crates/pobr-data/src/catalog/base_item_overrides.rs index f0f22aaf..dd540e56 100644 --- a/crates/pobr-data/src/catalog/base_item_overrides.rs +++ b/crates/pobr-data/src/catalog/base_item_overrides.rs @@ -40,6 +40,14 @@ pub struct BaseItemOverrideEntry { /// spawn weight 判定(tier 反查)需要全集。消费侧与 base tags **并集** merge。 #[serde(default, skip_serializing_if = "Option::is_none")] pub tags: Option>, + /// 基底属性需求(vendor `req = { str/dex/int }`;消费侧写入 + /// [`super::BaseItemDef::req_str`] 等,装备需求快照的取数源)。 + #[serde(default, skip_serializing_if = "Option::is_none")] + pub req_str: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub req_dex: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub req_int: Option, } /// `overlay/base_item_overrides.json` 顶层(消费侧视角:`_meta` 头部为生成溯源 diff --git a/crates/pobr-data/src/catalog/items.rs b/crates/pobr-data/src/catalog/items.rs index ed2cdbb6..06979494 100644 --- a/crates/pobr-data/src/catalog/items.rs +++ b/crates/pobr-data/src/catalog/items.rs @@ -51,6 +51,18 @@ pub struct BaseItemDef { /// 非 charm / 无 buff 为空。 #[serde(default, skip_serializing_if = "Vec::is_empty")] pub charm_buff: Vec, + /// 基底属性需求(vendor `Data/Bases/*.lua` 的 `req = { str/dex/int }`;GGG + /// `.dat` 对应表 bundle 不可得,走 `overlay/base_item_overrides.json` 抽取 + /// merge——与 [`Self::spirit`] 同款兜底)。消费方 = 装备需求快照 + /// `RequirementsOn`(vendor CalcPerform.lua:1848-1857, + /// Smith『Gain Armour equal to 150% of total Strength Requirements …』)。 + /// 无需求为 0。 + #[serde(default, skip_serializing_if = "is_zero_u32")] + pub req_str: u32, + #[serde(default, skip_serializing_if = "is_zero_u32")] + pub req_dex: u32, + #[serde(default, skip_serializing_if = "is_zero_u32")] + pub req_int: u32, } /// 武器基底数值(`WeaponTypes.dat` 外键解析;攻击技能伤害的基底,对照 PoB2 diff --git a/crates/pobr-data/src/catalog/tree.rs b/crates/pobr-data/src/catalog/tree.rs index 6f316c15..b315db13 100644 --- a/crates/pobr-data/src/catalog/tree.rs +++ b/crates/pobr-data/src/catalog/tree.rs @@ -101,6 +101,12 @@ pub struct PassiveNodeDef { /// `#[serde(default)]` 兼容;非 switchable 节点恒为空。 #[serde(default, skip_serializing_if = "Vec::is_empty")] pub variants: Vec, + /// Smith of Kitava 身甲连接 notable 标记(vendor `tree.lua` `applyToArmour`, + /// 由 `pobr-data-adapter --tree-coords` 回填):已分配数 → + /// `Multiplier:AllocatedConnectedNotable`(vendor CalcSetup.lua:840-841, + /// 消费方 = Masterwork『+200 to Armour for each Connected Notable …』)。 + #[serde(default, skip_serializing_if = "core::ops::Not::not")] + pub apply_to_armour: bool, } /// 某个职业的飞升摘要。 diff --git a/crates/pobr-gamedata/src/domains/base_item_overrides.rs b/crates/pobr-gamedata/src/domains/base_item_overrides.rs index f15b9483..592fd0bd 100644 --- a/crates/pobr-gamedata/src/domains/base_item_overrides.rs +++ b/crates/pobr-gamedata/src/domains/base_item_overrides.rs @@ -102,6 +102,16 @@ pub fn apply_base_item_overrides(bases: &mut [BaseItemDef], overrides: &BaseItem merged.dedup(); base.tags = merged; } + // 基底属性需求(vendor `req = { str/dex/int }`,装备需求快照取数源)。 + if let Some(req) = entry.req_str { + base.req_str = req; + } + if let Some(req) = entry.req_dex { + base.req_dex = req; + } + if let Some(req) = entry.req_int { + base.req_int = req; + } } } @@ -125,6 +135,9 @@ mod tests { armour, spirit: None, charm_buff: Vec::new(), + req_str: 0, + req_dex: 0, + req_int: 0, } } @@ -149,6 +162,9 @@ mod tests { let overrides = BaseItemOverridesDef { overrides: vec![ BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Crude Tower Shield".to_string(), block_chance: Some(26.0), spirit: None, @@ -157,6 +173,9 @@ mod tests { tags: None, }, BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Omen Sceptre".to_string(), block_chance: None, spirit: Some(100), @@ -183,6 +202,9 @@ mod tests { let mut bases = vec![base("Phantom Buckler", None)]; let overrides = BaseItemOverridesDef { overrides: vec![BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Phantom Buckler".to_string(), block_chance: Some(20.0), spirit: None, @@ -203,6 +225,9 @@ mod tests { let mut bases = vec![base("Crude Tower Shield", Some(armour_stats(18)))]; let overrides = BaseItemOverridesDef { overrides: vec![BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Removed Legacy Shield".to_string(), block_chance: Some(30.0), spirit: None, @@ -233,6 +258,9 @@ mod tests { let overrides = BaseItemOverridesDef { overrides: vec![ BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Makeshift Crossbow".to_string(), block_chance: None, spirit: None, @@ -241,6 +269,9 @@ mod tests { tags: None, }, BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Weaponless Oddity".to_string(), block_chance: None, spirit: None, @@ -278,6 +309,9 @@ mod tests { let overrides = BaseItemOverridesDef { overrides: vec![ BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Ruby Charm".to_string(), block_chance: None, spirit: None, @@ -286,6 +320,9 @@ mod tests { tags: None, }, BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Topaz Charm".to_string(), block_chance: None, spirit: None, @@ -295,6 +332,9 @@ mod tests { }, // charm_buff None → 不动(保持空),且校验非 charm 基底不受扰动。 BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Crude Tower Shield".to_string(), block_chance: Some(26.0), spirit: None, diff --git a/crates/pobr-tree/tests/radius_jewel.rs b/crates/pobr-tree/tests/radius_jewel.rs index b517db6a..535201fd 100644 --- a/crates/pobr-tree/tests/radius_jewel.rs +++ b/crates/pobr-tree/tests/radius_jewel.rs @@ -40,6 +40,7 @@ fn fixture_positions() -> HashMap { fn node(skill: u32, kind: PassiveNodeKind) -> PassiveNodeDef { PassiveNodeDef { + apply_to_armour: false, skill, id: format!("node_{skill}"), name: Some(format!("Node {skill}")), diff --git a/crates/pobr-tree/tests/tree.rs b/crates/pobr-tree/tests/tree.rs index 7d4a3b58..2a85c022 100644 --- a/crates/pobr-tree/tests/tree.rs +++ b/crates/pobr-tree/tests/tree.rs @@ -273,6 +273,7 @@ fn compute_node_mods_splits_multiline_stats_into_separate_texts() { fn from_nodes_round_trips_with_positions() { let nodes = vec![ PassiveNodeDef { + apply_to_armour: false, skill: 1, id: "a".into(), name: None, @@ -288,6 +289,7 @@ fn from_nodes_round_trips_with_positions() { variants: vec![], }, PassiveNodeDef { + apply_to_armour: false, skill: 2, id: "b".into(), name: None, diff --git a/data/4.5.4.3/base/passive_tree.json b/data/4.5.4.3/base/passive_tree.json index af706ee7..8d07dc1f 100644 --- a/data/4.5.4.3/base/passive_tree.json +++ b/data/4.5.4.3/base/passive_tree.json @@ -249,7 +249,8 @@ "orbit_index": 0, "x": -15422.891927, "y": 5717.99705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 116, @@ -10526,7 +10527,8 @@ "orbit_index": 0, "x": -15190.211927, "y": 5992.68705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 8531, @@ -12762,7 +12764,8 @@ "orbit_index": 0, "x": -15031.611927, "y": 6079.39705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 10011, @@ -17727,7 +17730,8 @@ "orbit_index": 0, "x": -14861.501927, "y": 6123.01705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 13777, @@ -20686,7 +20690,8 @@ "orbit_index": 0, "x": -14062.601927, "y": 5558.05705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 16311, @@ -25715,7 +25720,8 @@ "orbit_index": 0, "x": -14136.951927, "y": 5717.99705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 20205, @@ -28867,7 +28873,8 @@ "orbit_index": 0, "x": -15325.041927, "y": 5867.83705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 22927, @@ -32032,7 +32039,8 @@ "orbit_index": 0, "x": -14678.231927, "y": 6123.01705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 25446, @@ -63080,7 +63088,8 @@ "orbit_index": 0, "x": -14511.001927, "y": 6079.39705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 49356, @@ -77820,7 +77829,8 @@ "orbit_index": 0, "x": -14353.191927, "y": 5992.68705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 60916, @@ -77914,7 +77924,8 @@ "orbit_index": 0, "x": -14229.211927, "y": 5867.83705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 61042, @@ -83073,7 +83084,8 @@ "orbit_index": 0, "x": -15479.931927, "y": 5558.05705, - "ascendancy_id": "Warrior3" + "ascendancy_id": "Warrior3", + "apply_to_armour": true }, { "skill": 64990, diff --git a/data/4.5.4.3/overlay/base_item_overrides.json b/data/4.5.4.3/overlay/base_item_overrides.json index 70c6b834..3dbf8576 100644 --- a/data/4.5.4.3/overlay/base_item_overrides.json +++ b/data/4.5.4.3/overlay/base_item_overrides.json @@ -3,8 +3,8 @@ "schema": "base_item_overrides/v1", "generator": "sync-pob-catalog extract-bases", "vendor": "PathOfBuilding-PoE2", - "vendor_commit": "29ab8262dd4a867eac56165afb8391a509e34a0e", - "vendor_commit_subject": "29ab826 Add support for Darkness Enthroned and Atziri's Splendour augment mods (#2343)", + "vendor_commit": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", + "vendor_commit_subject": "ce8bffaba31f8e68cfce70579e1c96465e7c133c", "extracted_files": [ "Data/Bases/amulet.lua", "Data/Bases/axe.lua", @@ -46,7 +46,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Absent Amulet", @@ -62,7 +63,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Abyssal Flail", @@ -72,7 +74,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 89, + "req_int": 36 }, { "name": "Abyssal Signet", @@ -89,7 +93,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 60 }, { "name": "Adherent Bow", @@ -100,7 +105,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 104 }, { "name": "Adherent Cuffs", @@ -110,7 +116,9 @@ "gloves", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 55, + "req_int": 55 }, { "name": "Adherent Leggings", @@ -119,7 +127,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 34, + "req_int": 34 }, { "name": "Adherent's Raiment", @@ -128,7 +138,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 94 }, { "name": "Adorned Gloves", @@ -137,7 +148,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 80 }, { "name": "Adorned Wraps", @@ -146,7 +158,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Aegis Buckler", @@ -158,7 +172,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 91 }, { "name": "Aegis Quarterstaff", @@ -168,7 +183,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Aged Cuffs", @@ -178,7 +195,9 @@ "gloves", "maraketh_basetype", "str_int_armour" - ] + ], + "req_str": 13, + "req_int": 13 }, { "name": "Aged Tower Shield", @@ -189,7 +208,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 64 }, { "name": "Akoyan Club", @@ -200,7 +220,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Akoyan Spear", @@ -211,7 +232,10 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 50, + "req_dex": 127, + "req_int": 90 }, { "name": "Alloy Crossbow", @@ -224,7 +248,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 28, + "req_dex": 28 }, { "name": "Alpha Talisman", @@ -235,7 +261,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 98, + "req_int": 72 }, { "name": "Altar Robe", @@ -245,7 +273,8 @@ "default", "int_armour", "vaal_basetype" - ] + ], + "req_int": 65 }, { "name": "Amber Amulet", @@ -284,7 +313,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 71 }, { "name": "Ancestral Mail", @@ -294,7 +324,9 @@ "default", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Ancestral Tiara", @@ -304,7 +336,8 @@ "helmet", "int_armour", "karui_basetype" - ] + ], + "req_int": 115 }, { "name": "Anchorite Garb", @@ -314,7 +347,9 @@ "default", "dex_int_armour", "vaal_basetype" - ] + ], + "req_dex": 31, + "req_int": 31 }, { "name": "Ancient Buckler", @@ -326,7 +361,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 107 }, { "name": "Ancient Cuffs", @@ -335,7 +371,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Ancient Gauntlets", @@ -344,7 +382,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 52, + "req_dex": 52 }, { "name": "Ancient Greatblade", @@ -354,7 +394,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 49, + "req_dex": 49 }, { "name": "Ancient Leggings", @@ -363,7 +405,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 56, + "req_int": 56 }, { "name": "Ancient Mail", @@ -372,7 +416,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Ancient Mitts", @@ -381,7 +427,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 67 }, { "name": "Ancient Visor", @@ -390,7 +437,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Anima Quarterstaff", @@ -400,7 +449,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 72, + "req_int": 29 }, { "name": "Antidote Charm", @@ -421,7 +472,8 @@ "ezomyte_basetype", "focus", "int_armour" - ] + ], + "req_int": 17 }, { "name": "Anvil Maul", @@ -431,7 +483,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 134 }, { "name": "Apostle Leggings", @@ -440,7 +493,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 51, + "req_int": 51 }, { "name": "Arcane Dirk", @@ -450,7 +505,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 55, + "req_int": 55 }, { "name": "Arcane Raiment", @@ -459,7 +516,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 121 }, { "name": "Arced Claw", @@ -470,7 +528,8 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 60 }, { "name": "Arced Longsword", @@ -481,7 +540,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 29, + "req_dex": 29 }, { "name": "Arched Greataxe", @@ -492,7 +553,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 25, + "req_dex": 12 }, { "name": "Archon Crown", @@ -501,7 +564,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Arcing Quarterstaff", @@ -511,7 +576,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 71, + "req_int": 29 }, { "name": "Armoured Cap", @@ -520,7 +587,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 91 }, { "name": "Armoured Claw", @@ -530,7 +598,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 92 }, { "name": "Armoured Vest", @@ -539,7 +608,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 121 }, { "name": "Aromatic Sceptre", @@ -548,7 +618,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 92 }, { "name": "Array Buckler", @@ -560,7 +631,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 78 }, { "name": "Arrayed Focus", @@ -569,7 +641,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 77 }, { "name": "Artillery Bow", @@ -581,7 +654,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 80 }, { "name": "Ascetic Garb", @@ -590,7 +664,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Ashbark Talisman", @@ -601,7 +677,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 94, + "req_int": 67 }, { "name": "Ashen Staff", @@ -622,7 +700,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Attuned Focus", @@ -631,7 +711,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 81 }, { "name": "Attuned Wand", @@ -651,7 +732,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Austere Garb", @@ -661,7 +744,9 @@ "default", "dex_int_armour", "karui_basetype" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Avian Mask", @@ -670,7 +755,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 46, + "req_int": 46 }, { "name": "Avian Robe", @@ -679,7 +766,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 72 }, { "name": "Avian Targe", @@ -690,7 +778,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Avian Tiara", @@ -699,7 +789,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 69 }, { "name": "Azure Amulet", @@ -715,7 +806,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 32, + "req_int": 32 }, { "name": "Bandit Hatchet", @@ -726,7 +819,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 30, + "req_dex": 14 }, { "name": "Bandit Mace", @@ -736,7 +831,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 104 }, { "name": "Bandit Mask", @@ -745,7 +841,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 38, + "req_int": 38 }, { "name": "Bangled Sandals", @@ -754,7 +852,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 68 }, { "name": "Barbarian Plate", @@ -763,7 +862,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 72 }, { "name": "Barbarous Quarterstaff", @@ -773,7 +873,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 67, + "req_int": 27 }, { "name": "Barbed Bracers", @@ -782,7 +884,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 94 }, { "name": "Barbed Spear", @@ -793,7 +896,9 @@ "spear", "vaal_basetype", "weapon" - ] + ], + "req_str": 20, + "req_dex": 47 }, { "name": "Baroque Gloves", @@ -802,7 +907,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 67 }, { "name": "Baroque Targe", @@ -813,7 +919,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Barricade Tower Shield", @@ -825,7 +933,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 25 }, { "name": "Barrier Quarterstaff", @@ -836,7 +945,9 @@ "vaal_basetype", "warstaff", "weapon" - ] + ], + "req_dex": 52, + "req_int": 22 }, { "name": "Bastion Sabatons", @@ -845,7 +956,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Battle Axe", @@ -855,7 +968,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 72, + "req_dex": 29 }, { "name": "Battle Pick", @@ -865,7 +980,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 91 }, { "name": "Beaded Circlet", @@ -875,7 +991,8 @@ "helmet", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 25 }, { "name": "Bearded Axe", @@ -886,7 +1003,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 17, + "req_dex": 9 }, { "name": "Bearskin Mantle", @@ -895,7 +1014,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 43, + "req_int": 43 }, { "name": "Biostatic Ring", @@ -917,7 +1038,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Blacksmith Maul", @@ -927,7 +1050,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 86 }, { "name": "Blacksteel Crest Shield", @@ -939,7 +1063,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 63, + "req_int": 63 }, { "name": "Blacksteel Gauntlets", @@ -949,7 +1075,9 @@ "gloves", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 55, + "req_dex": 55 }, { "name": "Blacksteel Sabatons", @@ -959,7 +1087,9 @@ "default", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Blacksteel Tower Shield", @@ -970,7 +1100,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 91 }, { "name": "Bladed Quarterstaff", @@ -980,7 +1111,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 81, + "req_int": 33 }, { "name": "Bladed Shoes", @@ -989,7 +1122,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 47, + "req_int": 47 }, { "name": "Bladed Trap", @@ -1009,7 +1144,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 86 }, { "name": "Blazon Crest Shield", @@ -1033,7 +1169,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 60, + "req_dex": 60 }, { "name": "Blessed Claymore", @@ -1044,7 +1182,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 14, + "req_dex": 14 }, { "name": "Bloodletting Dagger", @@ -1055,7 +1195,9 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 39, + "req_int": 39 }, { "name": "Bloodstone Amulet", @@ -1079,7 +1221,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 63, + "req_dex": 26 }, { "name": "Bold Sabatons", @@ -1088,7 +1232,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 47, + "req_dex": 47 }, { "name": "Bolstered Mitts", @@ -1098,7 +1244,8 @@ "gloves", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 35 }, { "name": "Bolting Quarterstaff", @@ -1108,7 +1255,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Bombard Crossbow", @@ -1121,7 +1270,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Bone Raiment", @@ -1131,7 +1282,8 @@ "default", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 28 }, { "name": "Bone Wand", @@ -1152,7 +1304,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 60 }, { "name": "Bound Bracers", @@ -1162,7 +1315,8 @@ "dex_armour", "gloves", "maraketh_basetype" - ] + ], + "req_dex": 22 }, { "name": "Bound Cuffs", @@ -1171,7 +1325,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 44, + "req_int": 44 }, { "name": "Bound Leggings", @@ -1180,7 +1336,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Bound Sandals", @@ -1189,7 +1347,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 86 }, { "name": "Braced Sabatons", @@ -1199,7 +1358,9 @@ "default", "maraketh_basetype", "str_dex_armour" - ] + ], + "req_str": 14, + "req_dex": 14 }, { "name": "Braced Tower Shield", @@ -1211,7 +1372,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 19 }, { "name": "Braided Cuffs", @@ -1220,7 +1382,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 32, + "req_int": 32 }, { "name": "Branched Spear", @@ -1230,7 +1394,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 30, + "req_dex": 75 }, { "name": "Breach Ring", @@ -1250,7 +1416,8 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_str": 60 }, { "name": "Brigand Mask", @@ -1259,7 +1426,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Brimmed Helm", @@ -1269,7 +1438,9 @@ "ezomyte_basetype", "helmet", "str_dex_armour" - ] + ], + "req_str": 7, + "req_dex": 7 }, { "name": "Broad Greatsword", @@ -1280,7 +1451,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 19, + "req_dex": 19 }, { "name": "Broad Spear", @@ -1291,7 +1464,9 @@ "spear", "vaal_basetype", "weapon" - ] + ], + "req_str": 26, + "req_dex": 55 }, { "name": "Broadhead Quiver", @@ -1309,7 +1484,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 9, + "req_dex": 9 }, { "name": "Bronze Greaves", @@ -1319,7 +1496,8 @@ "default", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 23 }, { "name": "Buckled Wraps", @@ -1329,7 +1507,9 @@ "dex_int_armour", "gloves", "karui_basetype" - ] + ], + "req_dex": 32, + "req_int": 32 }, { "name": "Bulwark Greaves", @@ -1338,7 +1518,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 86 }, { "name": "Bulwark Tower Shield", @@ -1349,7 +1530,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 77 }, { "name": "Burnished Gauntlets", @@ -1358,7 +1540,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Cabalist Helm", @@ -1367,7 +1551,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 42, + "req_dex": 42 }, { "name": "Calescent Hammer", @@ -1377,7 +1563,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 80 }, { "name": "Calm Leg", @@ -1392,7 +1579,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 35, + "req_int": 35 }, { "name": "Cannonade Crossbow", @@ -1404,7 +1593,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 58, + "req_dex": 58 }, { "name": "Captain Gauntlets", @@ -1413,7 +1604,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 35, + "req_dex": 35 }, { "name": "Carved Greaves", @@ -1422,7 +1615,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 78 }, { "name": "Carved Targe", @@ -1433,7 +1627,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 40, + "req_dex": 40 }, { "name": "Carving Hatchet", @@ -1444,7 +1640,9 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_str": 47, + "req_dex": 20 }, { "name": "Cassis Helm", @@ -1453,7 +1651,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 46, + "req_dex": 46 }, { "name": "Caster Arm", @@ -1468,7 +1668,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 93 }, { "name": "Cavalry Bow", @@ -1479,7 +1680,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 149 }, { "name": "Ceremonial Halberd", @@ -1489,7 +1691,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 72, + "req_dex": 29 }, { "name": "Ceremonial Robe", @@ -1498,7 +1702,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 98 }, { "name": "Chain Flail", @@ -1509,7 +1714,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 12 }, { "name": "Chain Mail", @@ -1529,7 +1735,8 @@ "helmet", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 38 }, { "name": "Chain Vestments", @@ -1538,7 +1745,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 45, + "req_int": 45 }, { "name": "Champion Cuirass", @@ -1547,7 +1756,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 92 }, { "name": "Champion Helm", @@ -1556,7 +1766,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Changeling Talisman", @@ -1576,7 +1788,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 51, + "req_int": 51 }, { "name": "Charred Shortsword", @@ -1587,7 +1801,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 23, + "req_dex": 23 }, { "name": "Chieftain Cuirass", @@ -1597,7 +1813,8 @@ "default", "karui_basetype", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Chiming Staff", @@ -1605,7 +1822,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 46 }, { "name": "Chiseled Targe", @@ -1617,7 +1835,9 @@ "str_dex_armour", "str_dex_shield", "vaal_basetype" - ] + ], + "req_str": 27, + "req_dex": 27 }, { "name": "Cinched Boots", @@ -1626,7 +1846,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 86 }, { "name": "Cinderbark Talisman", @@ -1637,7 +1858,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 15, + "req_int": 11 }, { "name": "Cinquedea", @@ -1647,7 +1870,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 63, + "req_int": 63 }, { "name": "Clamping Trap", @@ -1664,7 +1889,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 46, + "req_int": 46 }, { "name": "Clasped Sceptre", @@ -1673,7 +1900,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 65 }, { "name": "Clay Trap", @@ -1699,7 +1927,9 @@ "default", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Cloaked Mail", @@ -1709,7 +1939,9 @@ "default", "maraketh_basetype", "str_dex_armour" - ] + ], + "req_str": 25, + "req_dex": 25 }, { "name": "Closed Helm", @@ -1719,7 +1951,9 @@ "helmet", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Coiled Buckler", @@ -1731,7 +1965,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 73 }, { "name": "Coiled Trap", @@ -1764,7 +1999,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 83 }, { "name": "Combat Arm", @@ -1779,7 +2015,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Commander Greathelm", @@ -1789,7 +2027,8 @@ "helmet", "karui_basetype", "str_armour" - ] + ], + "req_str": 64 }, { "name": "Commander Sword", @@ -1799,7 +2038,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 53, + "req_dex": 53 }, { "name": "Commanding Arm", @@ -1817,7 +2058,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 41 }, { "name": "Condemned Talisman", @@ -1827,7 +2069,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 74, + "req_int": 52 }, { "name": "Conjurer Mantle", @@ -1836,7 +2080,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Conqueror Plate", @@ -1845,7 +2091,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Construct Crossbow", @@ -1858,7 +2105,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 39, + "req_dex": 39 }, { "name": "Construct Hammer", @@ -1869,7 +2118,8 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_str": 65 }, { "name": "Corona Amulet", @@ -1885,7 +2135,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 64 }, { "name": "Corroded Longsword", @@ -1906,7 +2157,8 @@ "dex_armour", "helmet", "karui_basetype" - ] + ], + "req_dex": 64 }, { "name": "Corsair Coat", @@ -1916,7 +2168,8 @@ "default", "dex_armour", "karui_basetype" - ] + ], + "req_dex": 121 }, { "name": "Corsair Vest", @@ -1926,7 +2179,8 @@ "default", "dex_armour", "karui_basetype" - ] + ], + "req_dex": 72 }, { "name": "Corvus Mantle", @@ -1935,7 +2189,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Coursing Spear", @@ -1945,7 +2201,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 27, + "req_dex": 67 }, { "name": "Covered Sabatons", @@ -1955,7 +2213,9 @@ "default", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Covert Hood", @@ -1964,7 +2224,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 79 }, { "name": "Cowled Helm", @@ -1974,7 +2235,9 @@ "helmet", "maraketh_basetype", "str_dex_armour" - ] + ], + "req_str": 22, + "req_dex": 22 }, { "name": "Crackling Quarterstaff", @@ -1985,7 +2248,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 25, + "req_int": 12 }, { "name": "Crescent Axe", @@ -1996,7 +2261,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 38, + "req_dex": 17 }, { "name": "Crescent Quarterstaff", @@ -2007,7 +2274,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 30, + "req_int": 14 }, { "name": "Crescent Targe", @@ -2019,7 +2288,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 22, + "req_dex": 22 }, { "name": "Crimson Amulet", @@ -2034,7 +2305,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 92 }, { "name": "Crone Knife", @@ -2045,7 +2317,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 9, + "req_int": 9 }, { "name": "Crossblade Spear", @@ -2056,7 +2330,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 26, + "req_dex": 63 }, { "name": "Crown Mace", @@ -2066,7 +2342,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 149 }, { "name": "Crucible Tower Shield", @@ -2078,7 +2355,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 64 }, { "name": "Crude Bow", @@ -2112,7 +2390,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 72, + "req_int": 51 }, { "name": "Crumbling Maul", @@ -2123,7 +2403,8 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 68 }, { "name": "Cryptic Crown", @@ -2133,7 +2414,9 @@ "helmet", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 63, + "req_int": 63 }, { "name": "Cryptic Helm", @@ -2142,7 +2425,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Cryptic Leggings", @@ -2152,7 +2437,9 @@ "default", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Crystal Focus", @@ -2162,7 +2449,8 @@ "focus", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 38 }, { "name": "Cultist Bow", @@ -2174,7 +2462,8 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 60 }, { "name": "Cultist Crown", @@ -2184,7 +2473,9 @@ "helmet", "maraketh_basetype", "str_int_armour" - ] + ], + "req_str": 15, + "req_int": 15 }, { "name": "Cultist Focus", @@ -2193,7 +2484,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 83 }, { "name": "Cultist Gauntlets", @@ -2202,7 +2494,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 52, + "req_dex": 52 }, { "name": "Cultist Greathammer", @@ -2213,7 +2507,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 41 }, { "name": "Cultist Tower Shield", @@ -2224,7 +2519,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 73 }, { "name": "Cumbrous Crossbow", @@ -2236,7 +2532,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Cursespeaker's Robe", @@ -2245,7 +2543,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 77 }, { "name": "Cutlass", @@ -2255,7 +2554,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Daggerfoot Shoes", @@ -2265,7 +2566,9 @@ "default", "dex_int_armour", "karui_basetype" - ] + ], + "req_dex": 59, + "req_int": 59 }, { "name": "Dark Blade", @@ -2275,7 +2578,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Dark Staff", @@ -2283,7 +2588,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 114 }, { "name": "Dart Trap", @@ -2300,7 +2606,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Death Mail", @@ -2309,7 +2617,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Death Mantle", @@ -2318,7 +2628,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Death Mask", @@ -2327,7 +2639,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Decorated Helm", @@ -2336,7 +2650,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Decrepifying Arm", @@ -2354,7 +2670,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 55, + "req_dex": 55 }, { "name": "Deerstalker Hood", @@ -2363,7 +2681,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 83 }, { "name": "Defiant Tower Shield", @@ -2374,7 +2693,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 82 }, { "name": "Deflective Arm", @@ -2391,7 +2711,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Dekharan Crest Shield", @@ -2402,7 +2724,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 42, + "req_int": 42 }, { "name": "Descry Crest Shield", @@ -2413,7 +2737,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 40, + "req_int": 40 }, { "name": "Desert Buckler", @@ -2426,7 +2752,8 @@ "dex_shield", "karui_basetype", "shield" - ] + ], + "req_dex": 115 }, { "name": "Desert Cap", @@ -2435,7 +2762,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 99 }, { "name": "Desert Circlet", @@ -2444,7 +2772,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 73 }, { "name": "Desolate Crossbow", @@ -2456,7 +2785,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 89 }, { "name": "Detailed Mitts", @@ -2466,7 +2797,8 @@ "gloves", "karui_basetype", "str_armour" - ] + ], + "req_str": 56 }, { "name": "Devotional Sceptre", @@ -2475,7 +2807,9 @@ "default", "onehand", "sceptre" - ] + ], + "req_str": 26, + "req_int": 63 }, { "name": "Devouring Sceptre", @@ -2484,7 +2818,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 60 }, { "name": "Devout Garb", @@ -2493,7 +2828,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Diamond", @@ -2514,7 +2851,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 109 }, { "name": "Distorted Amulet", @@ -2530,7 +2868,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Domed Helm", @@ -2539,7 +2879,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Double Belt", @@ -2559,7 +2901,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 99 }, { "name": "Doubled Gauntlets", @@ -2569,7 +2912,9 @@ "gloves", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 24, + "req_dex": 24 }, { "name": "Dousing Charm", @@ -2589,7 +2934,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 101 }, { "name": "Drakeskin Boots", @@ -2599,7 +2945,8 @@ "default", "dex_armour", "karui_basetype" - ] + ], + "req_dex": 108 }, { "name": "Dread Hatchet", @@ -2609,7 +2956,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 36 }, { "name": "Dreaming Quarterstaff", @@ -2620,7 +2969,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Druidic Circlet", @@ -2629,7 +2980,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 64 }, { "name": "Druidic Crown", @@ -2638,7 +2990,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Druidic Focus", @@ -2647,7 +3001,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 91 }, { "name": "Dualstring Bow", @@ -2659,7 +3014,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 52 }, { "name": "Dueling Wand", @@ -2667,7 +3023,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 114 }, { "name": "Dull Hatchet", @@ -2687,7 +3044,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 78 }, { "name": "Dusk Amulet", @@ -2716,7 +3074,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 22, + "req_dex": 22 }, { "name": "Edged Buckler", @@ -2729,7 +3089,8 @@ "dex_shield", "shield", "vaal_basetype" - ] + ], + "req_dex": 56 }, { "name": "Edged Claw", @@ -2740,7 +3101,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 52 }, { "name": "Effigial Tower Shield", @@ -2752,7 +3114,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 32 }, { "name": "Elaborate Sandals", @@ -2761,7 +3124,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 86 }, { "name": "Elegant Crossbow", @@ -2774,7 +3138,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 89 }, { "name": "Elegant Focus", @@ -2783,7 +3149,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 74 }, { "name": "Elegant Glaive", @@ -2794,7 +3161,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 30, + "req_dex": 14 }, { "name": "Elegant Greathelm", @@ -2803,7 +3172,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 77 }, { "name": "Elegant Greaves", @@ -2812,7 +3182,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 72 }, { "name": "Elegant Mitts", @@ -2821,7 +3192,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 64 }, { "name": "Elegant Plate", @@ -2830,7 +3202,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 86 }, { "name": "Elegant Slippers", @@ -2839,7 +3212,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 72 }, { "name": "Elegant Wraps", @@ -2848,7 +3222,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 48, + "req_int": 48 }, { "name": "Elementalist Robe", @@ -2858,7 +3234,8 @@ "default", "int_armour", "karui_basetype" - ] + ], + "req_int": 72 }, { "name": "Elite Greathelm", @@ -2868,7 +3245,8 @@ "helmet", "str_armour", "vaal_basetype" - ] + ], + "req_str": 48 }, { "name": "Embellished Gloves", @@ -2877,7 +3255,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 64 }, { "name": "Ember Greataxe", @@ -2887,7 +3266,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 70, + "req_dex": 28 }, { "name": "Emblem Crest Shield", @@ -2899,7 +3280,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 15, + "req_int": 15 }, { "name": "Embossed Boots", @@ -2909,7 +3292,8 @@ "default", "dex_armour", "maraketh_basetype" - ] + ], + "req_dex": 23 }, { "name": "Embroidered Boots", @@ -2918,7 +3302,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 86 }, { "name": "Embroidered Gloves", @@ -2927,7 +3312,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 64 }, { "name": "Emerald", @@ -2955,7 +3341,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 45, + "req_int": 45 }, { "name": "Energy Blade One Handed", @@ -2986,7 +3374,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 80 }, { "name": "Engraved Crest Shield", @@ -2997,7 +3386,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Engraved Crossbow", @@ -3009,7 +3400,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 82, + "req_dex": 82 }, { "name": "Engraved Focus", @@ -3019,7 +3412,8 @@ "focus", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 25 }, { "name": "Engraved Helm", @@ -3028,7 +3422,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Engraved Knife", @@ -3039,7 +3435,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 29, + "req_int": 29 }, { "name": "Enlightened Robe", @@ -3048,7 +3446,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 121 }, { "name": "Esoteric Crossbow", @@ -3060,7 +3459,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Ethereal Blade", @@ -3080,7 +3481,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 45, + "req_int": 45 }, { "name": "Evasive Leg", @@ -3096,7 +3499,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 97 }, { "name": "Executioner Greataxe", @@ -3107,7 +3511,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 17, + "req_dex": 9 }, { "name": "Explorer Armour", @@ -3117,7 +3523,9 @@ "default", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Exquisite Vest", @@ -3126,7 +3534,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 121 }, { "name": "Extended Cleaver", @@ -3137,7 +3546,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 25, + "req_dex": 12 }, { "name": "Face Mask", @@ -3147,7 +3558,9 @@ "dex_int_armour", "ezomyte_basetype", "helmet" - ] + ], + "req_dex": 10, + "req_int": 10 }, { "name": "Faithful Leggings", @@ -3156,7 +3569,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 47, + "req_int": 47 }, { "name": "Falchion", @@ -3167,7 +3582,9 @@ "sword", "vaal_basetype", "weapon" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Falconer's Jacket", @@ -3176,7 +3593,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Familial Talisman", @@ -3187,7 +3606,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 21, + "req_int": 16 }, { "name": "Fanatic Bow", @@ -3198,7 +3619,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 163 }, { "name": "Fanatic Greathammer", @@ -3208,7 +3630,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Fang Talisman", @@ -3219,7 +3642,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_int": 68 }, { "name": "Faridun Mask", @@ -3228,7 +3653,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 54, + "req_int": 54 }, { "name": "Feathered Mitts", @@ -3237,7 +3664,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 73 }, { "name": "Feathered Raiment", @@ -3247,7 +3675,8 @@ "default", "int_armour", "karui_basetype" - ] + ], + "req_int": 121 }, { "name": "Feathered Robe", @@ -3257,7 +3686,8 @@ "default", "ezomyte_basetype", "int_armour" - ] + ], + "req_int": 11 }, { "name": "Feathered Sandals", @@ -3267,7 +3697,8 @@ "default", "int_armour", "vaal_basetype" - ] + ], + "req_int": 45 }, { "name": "Feathered Targe", @@ -3279,7 +3710,9 @@ "str_dex_armour", "str_dex_shield", "vaal_basetype" - ] + ], + "req_str": 30, + "req_dex": 30 }, { "name": "Feathered Tiara", @@ -3289,7 +3722,8 @@ "helmet", "int_armour", "vaal_basetype" - ] + ], + "req_int": 48 }, { "name": "Felled Greatclub", @@ -3310,7 +3744,8 @@ "dex_armour", "ezomyte_basetype", "helmet" - ] + ], + "req_dex": 17 }, { "name": "Fierce Greathelm", @@ -3320,7 +3755,8 @@ "helmet", "karui_basetype", "str_armour" - ] + ], + "req_str": 73 }, { "name": "Fine Belt", @@ -3339,7 +3775,8 @@ "dex_armour", "gloves", "karui_basetype" - ] + ], + "req_dex": 56 }, { "name": "Fire Quiver", @@ -3356,7 +3793,8 @@ "dex_armour", "ezomyte_basetype", "gloves" - ] + ], + "req_dex": 16 }, { "name": "Fishing Rod", @@ -3384,7 +3822,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Flanged Mace", @@ -3394,7 +3834,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 134 }, { "name": "Flared Boots", @@ -3404,7 +3845,8 @@ "default", "dex_armour", "karui_basetype" - ] + ], + "req_dex": 60 }, { "name": "Flared Mace", @@ -3414,7 +3856,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 86 }, { "name": "Flax Sandals", @@ -3424,7 +3867,8 @@ "default", "int_armour", "karui_basetype" - ] + ], + "req_int": 60 }, { "name": "Flexed Crossbow", @@ -3436,7 +3880,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 89 }, { "name": "Flexile Sabatons", @@ -3445,7 +3891,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Flowing Raiment", @@ -3454,7 +3902,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 121 }, { "name": "Flying Spear", @@ -3464,7 +3913,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 50, + "req_dex": 127 }, { "name": "Forest Crown", @@ -3473,7 +3924,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Forge Maul", @@ -3484,7 +3937,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 23 }, { "name": "Forked Claw", @@ -3495,7 +3949,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 31 }, { "name": "Forked Spear", @@ -3506,7 +3961,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 17, + "req_dex": 38 }, { "name": "Forking Belt", @@ -3525,7 +3982,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Fortress Sabatons", @@ -3534,7 +3992,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 56, + "req_dex": 56 }, { "name": "Fortress Tower Shield", @@ -3545,7 +4005,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 99 }, { "name": "Frayed Shoes", @@ -3555,7 +4016,9 @@ "default", "dex_int_armour", "ezomyte_basetype" - ] + ], + "req_dex": 7, + "req_int": 7 }, { "name": "Freebooter Cap", @@ -3565,7 +4028,8 @@ "dex_armour", "helmet", "karui_basetype" - ] + ], + "req_dex": 115 }, { "name": "Frenzied Talisman", @@ -3576,7 +4040,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 28, + "req_int": 20 }, { "name": "Frigid Wand", @@ -3588,7 +4054,8 @@ "no_physical_spell_mods", "onehand", "wand" - ] + ], + "req_int": 80 }, { "name": "Full Plate", @@ -3598,7 +4065,8 @@ "default", "str_armour", "vaal_basetype" - ] + ], + "req_str": 54 }, { "name": "Fungal Talisman", @@ -3609,7 +4077,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 96, + "req_int": 70 }, { "name": "Fur Plate", @@ -3619,7 +4089,8 @@ "default", "ezomyte_basetype", "str_armour" - ] + ], + "req_str": 10 }, { "name": "Fur-lined Targe", @@ -3630,7 +4101,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Furtive Wraps", @@ -3639,7 +4112,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 36, + "req_int": 36 }, { "name": "Fury Cleaver", @@ -3649,7 +4124,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 67, + "req_dex": 27 }, { "name": "Fury Talisman", @@ -3660,7 +4137,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 67, + "req_int": 48 }, { "name": "Gallant Helm", @@ -3669,7 +4148,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Galvanic Wand", @@ -3681,7 +4162,8 @@ "no_physical_spell_mods", "onehand", "wand" - ] + ], + "req_int": 46 }, { "name": "Gargantuan Life Flask", @@ -3716,7 +4198,9 @@ "dex_int_armour", "ezomyte_basetype", "gloves" - ] + ], + "req_dex": 6, + "req_int": 6 }, { "name": "Gelid Staff", @@ -3739,7 +4223,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 163 }, { "name": "Gemini Crossbow", @@ -3751,7 +4236,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 89 }, { "name": "Giant Life Flask", @@ -3777,7 +4264,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 114 }, { "name": "Gilded Vestments", @@ -3786,7 +4274,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 46, + "req_int": 46 }, { "name": "Gilt Leggings", @@ -3795,7 +4285,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Glacial Fortress", @@ -3806,7 +4298,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 99 }, { "name": "Gladiator Armour", @@ -3815,7 +4308,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Gladiatorial Helm", @@ -3825,7 +4320,9 @@ "helmet", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Glass Shank", @@ -3845,7 +4342,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 52, + "req_int": 52 }, { "name": "Gloam Amulet", @@ -3870,7 +4369,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Glowering Crest Shield", @@ -3881,7 +4381,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Gold Amulet", @@ -3898,7 +4400,8 @@ "helmet", "int_armour", "vaal_basetype" - ] + ], + "req_int": 58 }, { "name": "Gold Gloves", @@ -3907,7 +4410,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 73 }, { "name": "Gold Ring", @@ -3926,7 +4430,9 @@ "gloves", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 24, + "req_int": 24 }, { "name": "Golden Blade", @@ -3989,7 +4495,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Golden Mantle", @@ -4000,7 +4508,10 @@ "demigods", "not_for_sale", "str_dex_int_armour" - ] + ], + "req_str": 7, + "req_dex": 7, + "req_int": 7 }, { "name": "Golden Obi", @@ -4023,7 +4534,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Golden Visage", @@ -4052,7 +4565,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Goldworked Tower Shield", @@ -4063,7 +4578,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 86 }, { "name": "Goliath Greathelm", @@ -4072,7 +4588,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 81 }, { "name": "Gothic Quarterstaff", @@ -4083,7 +4600,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 18, + "req_int": 9 }, { "name": "Graceful Quarterstaff", @@ -4093,7 +4612,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 77, + "req_int": 31 }, { "name": "Grand Bracers", @@ -4102,7 +4623,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 87 }, { "name": "Grand Cuisses", @@ -4111,7 +4633,10 @@ "boots", "default", "str_dex_int_armour" - ] + ], + "req_str": 34, + "req_dex": 34, + "req_int": 34 }, { "name": "Grand Life Flask", @@ -4136,7 +4661,10 @@ "default", "gloves", "str_dex_int_armour" - ] + ], + "req_str": 32, + "req_dex": 32, + "req_int": 32 }, { "name": "Grand Mitts", @@ -4145,7 +4673,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Grand Regalia", @@ -4154,7 +4683,10 @@ "body_armour", "default", "str_dex_int_armour" - ] + ], + "req_str": 41, + "req_dex": 41, + "req_int": 41 }, { "name": "Grand Spear", @@ -4164,7 +4696,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 68, + "req_dex": 109 }, { "name": "Grand Targe", @@ -4175,7 +4709,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Grand Visage", @@ -4184,7 +4720,10 @@ "default", "helmet", "str_dex_int_armour" - ] + ], + "req_str": 36, + "req_dex": 36, + "req_int": 36 }, { "name": "Grasping Mail", @@ -4193,7 +4732,10 @@ "body_armour", "default", "str_dex_int_armour" - ] + ], + "req_str": 41, + "req_dex": 41, + "req_int": 41 }, { "name": "Grasping Ring", @@ -4212,7 +4754,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 80, + "req_int": 32 }, { "name": "Greatbow", @@ -4223,7 +4767,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 92, + "req_dex": 92 }, { "name": "Greater Life Flask", @@ -4249,7 +4795,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 92 }, { "name": "Grim Gloves", @@ -4258,7 +4805,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 80 }, { "name": "Grinning Mask", @@ -4268,7 +4816,9 @@ "dex_int_armour", "helmet", "karui_basetype" - ] + ], + "req_dex": 63, + "req_int": 63 }, { "name": "Grounding Charm", @@ -4289,7 +4839,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 63, + "req_int": 26 }, { "name": "Guarded Helm", @@ -4299,7 +4851,9 @@ "ezomyte_basetype", "helmet", "str_dex_armour" - ] + ], + "req_str": 11, + "req_dex": 11 }, { "name": "Guardian Bow", @@ -4310,7 +4864,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 163 }, { "name": "Guardian Greathelm", @@ -4319,7 +4874,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 91 }, { "name": "Guardian Quarterstaff", @@ -4329,7 +4885,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 85, + "req_int": 34 }, { "name": "Guardian Spear", @@ -4340,7 +4898,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 50, + "req_dex": 127 }, { "name": "Guarding Arm", @@ -4358,7 +4918,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 99 }, { "name": "Hallowed Crown", @@ -4367,7 +4928,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 42, + "req_int": 42 }, { "name": "Hallowed Focus", @@ -4376,7 +4939,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 86 }, { "name": "Hallowed Sceptre", @@ -4385,7 +4949,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 114 }, { "name": "Hardened Bracers", @@ -4394,7 +4959,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 64 }, { "name": "Hardleather Coat", @@ -4403,7 +4969,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 89 }, { "name": "Hardwood Spear", @@ -4435,7 +5002,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 52, + "req_int": 52 }, { "name": "Havoc Raiment", @@ -4444,7 +5013,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 121 }, { "name": "Hawker's Jacket", @@ -4453,7 +5023,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 54, + "req_int": 54 }, { "name": "Heartcarver Mantle", @@ -4462,7 +5034,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 54, + "req_int": 54 }, { "name": "Heartwood Shortbow", @@ -4473,7 +5047,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 134 }, { "name": "Heavy Belt", @@ -4493,7 +5068,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 114 }, { "name": "Heavy Crown", @@ -4503,7 +5079,9 @@ "helmet", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 27, + "req_int": 27 }, { "name": "Heavy Greathammer", @@ -4513,7 +5091,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 104 }, { "name": "Heavy Plate", @@ -4522,7 +5101,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 94 }, { "name": "Hefty Quarterstaff", @@ -4533,7 +5113,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 63, + "req_int": 26 }, { "name": "Heirloom Cuffs", @@ -4542,7 +5124,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 35, + "req_int": 35 }, { "name": "Helix Spear", @@ -4552,7 +5136,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 36, + "req_dex": 89 }, { "name": "Heraldric Tower Shield", @@ -4564,7 +5150,8 @@ "str_armour", "str_shield", "vaal_basetype" - ] + ], + "req_str": 48 }, { "name": "Hermit Garb", @@ -4583,7 +5170,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Hewn Mask", @@ -4593,7 +5182,9 @@ "dex_int_armour", "ezomyte_basetype", "helmet" - ] + ], + "req_dex": 7, + "req_int": 7 }, { "name": "Hexer's Robe", @@ -4603,7 +5194,8 @@ "default", "ezomyte_basetype", "int_armour" - ] + ], + "req_int": 21 }, { "name": "Holy Flail", @@ -4614,7 +5206,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 18, + "req_int": 9 }, { "name": "Homeguard Greathelm", @@ -4623,7 +5217,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 73 }, { "name": "Hooded Mask", @@ -4633,7 +5228,9 @@ "dex_int_armour", "helmet", "maraketh_basetype" - ] + ], + "req_dex": 15, + "req_int": 15 }, { "name": "Hook Axe", @@ -4644,7 +5241,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 9 }, { "name": "Hooked Claw", @@ -4655,7 +5253,8 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 65 }, { "name": "Horned Crown", @@ -4665,7 +5264,9 @@ "ezomyte_basetype", "helmet", "str_int_armour" - ] + ], + "req_str": 10, + "req_int": 10 }, { "name": "Howling Talisman", @@ -4676,7 +5277,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_int": 45 }, { "name": "Hunter Hood", @@ -4686,7 +5289,8 @@ "dex_armour", "helmet", "vaal_basetype" - ] + ], + "req_dex": 48 }, { "name": "Hunting Bracers", @@ -4695,7 +5299,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 56 }, { "name": "Hunting Coat", @@ -4704,7 +5309,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 77 }, { "name": "Hunting Shoes", @@ -4714,7 +5320,9 @@ "default", "dex_int_armour", "vaal_basetype" - ] + ], + "req_dex": 26, + "req_int": 26 }, { "name": "Hunting Spear", @@ -4725,7 +5333,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 9, + "req_dex": 17 }, { "name": "Icicle Flail", @@ -4735,7 +5345,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 66, + "req_int": 27 }, { "name": "Icicle Staff", @@ -4747,7 +5359,8 @@ "no_physical_spell_mods", "staff", "twohand" - ] + ], + "req_int": 80 }, { "name": "Imperial Greathelm", @@ -4757,7 +5370,8 @@ "helmet", "karui_basetype", "str_armour" - ] + ], + "req_str": 115 }, { "name": "Imperial Robe", @@ -4766,7 +5380,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 83 }, { "name": "Incense Trap", @@ -4783,7 +5398,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 46, + "req_int": 46 }, { "name": "Intricate Crest Shield", @@ -4794,7 +5411,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Intricate Gloves", @@ -4804,7 +5423,8 @@ "gloves", "int_armour", "vaal_basetype" - ] + ], + "req_int": 42 }, { "name": "Intricate Trap", @@ -4834,7 +5454,8 @@ "dex_shield", "maraketh_basetype", "shield" - ] + ], + "req_dex": 25 }, { "name": "Iron Crown", @@ -4844,7 +5465,9 @@ "ezomyte_basetype", "helmet", "str_int_armour" - ] + ], + "req_str": 7, + "req_int": 7 }, { "name": "Iron Cuirass", @@ -4854,7 +5477,8 @@ "default", "ezomyte_basetype", "str_armour" - ] + ], + "req_str": 21 }, { "name": "Iron Flail", @@ -4865,7 +5489,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 25, + "req_int": 12 }, { "name": "Iron Greatsword", @@ -4876,7 +5502,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 9, + "req_dex": 9 }, { "name": "Iron Greaves", @@ -4886,7 +5514,8 @@ "default", "ezomyte_basetype", "str_armour" - ] + ], + "req_str": 17 }, { "name": "Iron Ring", @@ -4905,7 +5534,9 @@ "default", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Ironhead Spear", @@ -4916,7 +5547,8 @@ "onehand", "spear", "weapon" - ] + ], + "req_dex": 10 }, { "name": "Ironmail Gauntlets", @@ -4925,7 +5557,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 32, + "req_dex": 32 }, { "name": "Ironwood Greathammer", @@ -4935,7 +5569,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Ironwood Shortbow", @@ -4946,7 +5581,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 134 }, { "name": "Ironwood Targe", @@ -4957,7 +5593,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Itinerant Jacket", @@ -4966,7 +5604,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 48, + "req_int": 48 }, { "name": "Jade Amulet", @@ -4984,7 +5624,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 87 }, { "name": "Jade Talisman", @@ -4995,7 +5636,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 109, + "req_int": 65 }, { "name": "Jade Tiara", @@ -5005,7 +5648,8 @@ "helmet", "int_armour", "karui_basetype" - ] + ], + "req_int": 71 }, { "name": "Jagged Greataxe", @@ -5016,7 +5660,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 56, + "req_dex": 23 }, { "name": "Jagged Spear", @@ -5026,7 +5672,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 33, + "req_dex": 81 }, { "name": "Jewelled Gloves", @@ -5036,7 +5684,8 @@ "gloves", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 34 }, { "name": "Jingling Buckler", @@ -5048,7 +5697,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 83 }, { "name": "Jingling Crest Shield", @@ -5060,7 +5710,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 23, + "req_int": 23 }, { "name": "Jingling Focus", @@ -5069,7 +5721,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 73 }, { "name": "Juggernaut Plate", @@ -5079,7 +5732,8 @@ "default", "karui_basetype", "str_armour" - ] + ], + "req_str": 72 }, { "name": "Jungle Tiara", @@ -5088,7 +5742,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 83 }, { "name": "Kalguuran Cuffs", @@ -5098,7 +5753,9 @@ "gloves", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 32, + "req_int": 32 }, { "name": "Kalguuran Forgehammer", @@ -5117,7 +5774,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 107 }, { "name": "Keen Greatsword", @@ -5127,7 +5785,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Keth Raiment", @@ -5137,7 +5797,8 @@ "default", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 47 }, { "name": "Keyblade", @@ -5167,7 +5828,9 @@ "default", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Knightly Mitts", @@ -5176,7 +5839,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Kris Knife", @@ -5186,7 +5850,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 49, + "req_int": 49 }, { "name": "Lace Hood", @@ -5196,7 +5862,8 @@ "dex_armour", "helmet", "maraketh_basetype" - ] + ], + "req_dex": 25 }, { "name": "Laced Boots", @@ -5206,7 +5873,8 @@ "default", "dex_armour", "ezomyte_basetype" - ] + ], + "req_dex": 17 }, { "name": "Laced Sandals", @@ -5215,7 +5883,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 60 }, { "name": "Lamellar Mail", @@ -5224,7 +5893,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 46, + "req_dex": 46 }, { "name": "Lament Amulet", @@ -5244,7 +5915,8 @@ "dex_shield", "karui_basetype", "shield" - ] + ], + "req_dex": 64 }, { "name": "Lapis Amulet", @@ -5261,7 +5933,8 @@ "default", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 23 }, { "name": "Lavish Crown", @@ -5270,7 +5943,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Layered Gauntlets", @@ -5280,7 +5955,9 @@ "gloves", "maraketh_basetype", "str_dex_armour" - ] + ], + "req_str": 13, + "req_dex": 13 }, { "name": "Layered Vest", @@ -5289,7 +5966,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 86 }, { "name": "Lazuli Ring", @@ -5317,7 +5995,8 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 60 }, { "name": "Leather Buckler", @@ -5350,7 +6029,8 @@ "dex_armour", "helmet", "karui_basetype" - ] + ], + "req_dex": 71 }, { "name": "Leatherplate Boots", @@ -5359,7 +6039,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 69 }, { "name": "Lesser Life Flask", @@ -5384,7 +6065,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 99 }, { "name": "Light Halberd", @@ -5395,7 +6077,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 9 }, { "name": "Linen Belt", @@ -5414,7 +6097,9 @@ "dex_int_armour", "gloves", "maraketh_basetype" - ] + ], + "req_dex": 13, + "req_int": 13 }, { "name": "Lionheart Greaves", @@ -5423,7 +6108,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 68 }, { "name": "Lizardscale Boots", @@ -5433,7 +6119,8 @@ "default", "dex_armour", "vaal_basetype" - ] + ], + "req_dex": 45 }, { "name": "Lizardscale Coat", @@ -5442,7 +6129,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 98 }, { "name": "Long Belt", @@ -5462,7 +6150,8 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 9 }, { "name": "Lumbering Talisman", @@ -5473,7 +6162,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 60, + "req_int": 43 }, { "name": "Lumen Mace", @@ -5483,7 +6174,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 92 }, { "name": "Lunar Amulet", @@ -5500,7 +6192,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 115, + "req_int": 46 }, { "name": "Lupine Sceptre", @@ -5509,7 +6203,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 24 }, { "name": "Luxurious Robe", @@ -5518,7 +6213,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 82 }, { "name": "Luxurious Slippers", @@ -5527,7 +6223,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 93 }, { "name": "Magus Focus", @@ -5536,7 +6233,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 91 }, { "name": "Magus Tiara", @@ -5545,7 +6243,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 91 }, { "name": "Mail Belt", @@ -5564,7 +6263,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 45, + "req_int": 45 }, { "name": "Mail Coat", @@ -5573,7 +6274,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 121 }, { "name": "Mail Sabatons", @@ -5583,7 +6285,9 @@ "default", "ezomyte_basetype", "str_dex_armour" - ] + ], + "req_str": 7, + "req_dex": 7 }, { "name": "Mail Vestments", @@ -5593,7 +6297,9 @@ "default", "maraketh_basetype", "str_int_armour" - ] + ], + "req_str": 17, + "req_int": 17 }, { "name": "Mailed Crown", @@ -5602,7 +6308,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Maji Talisman", @@ -5613,7 +6321,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 100, + "req_int": 67 }, { "name": "Makeshift Crossbow", @@ -5637,7 +6347,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Mantled Mail", @@ -5646,7 +6358,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Marabout Garb", @@ -5656,7 +6370,9 @@ "default", "dex_int_armour", "maraketh_basetype" - ] + ], + "req_dex": 17, + "req_int": 17 }, { "name": "Maraketh Cuirass", @@ -5666,7 +6382,8 @@ "default", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 34 }, { "name": "Marauding Mace", @@ -5676,7 +6393,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Marching Mace", @@ -5686,7 +6404,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 96 }, { "name": "Martyr Crown", @@ -5696,7 +6415,9 @@ "helmet", "maraketh_basetype", "str_int_armour" - ] + ], + "req_str": 23, + "req_int": 23 }, { "name": "Masked Greathelm", @@ -5705,7 +6426,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 99 }, { "name": "Massive Greathammer", @@ -5715,7 +6437,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Massive Mitts", @@ -5725,7 +6448,8 @@ "gloves", "karui_basetype", "str_armour" - ] + ], + "req_str": 101 }, { "name": "Massive Spear", @@ -5735,7 +6459,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 40, + "req_dex": 80 }, { "name": "Medallion Trap", @@ -5768,7 +6494,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 69 }, { "name": "Mercenary Targe", @@ -5779,7 +6506,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 40, + "req_dex": 40 }, { "name": "Messer", @@ -5789,7 +6518,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Metalworked Tower Shield", @@ -5800,7 +6531,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 69 }, { "name": "Militant Bow", @@ -5811,7 +6543,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 109 }, { "name": "Mnemonic Ring", @@ -5830,7 +6563,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Monastic Garb", @@ -5839,7 +6573,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Monument Greataxe", @@ -5849,7 +6585,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 79, + "req_dex": 32 }, { "name": "Monument Greaves", @@ -5858,7 +6596,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 69 }, { "name": "Moon Dagger", @@ -5869,7 +6608,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 22, + "req_int": 22 }, { "name": "Morning Star", @@ -5880,7 +6621,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 80 }, { "name": "Mosaic Targe", @@ -5892,7 +6634,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Moulded Mitts", @@ -5902,7 +6646,8 @@ "gloves", "str_armour", "vaal_basetype" - ] + ], + "req_str": 42 }, { "name": "Mystic Raiment", @@ -5912,7 +6657,8 @@ "default", "int_armour", "karui_basetype" - ] + ], + "req_int": 78 }, { "name": "Narrow Hood", @@ -5921,7 +6667,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 73 }, { "name": "Nettle Talisman", @@ -5932,7 +6679,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 9, + "req_int": 8 }, { "name": "Noble Circlet", @@ -5941,7 +6690,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 74 }, { "name": "Noble Greathelm", @@ -5950,7 +6700,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 83 }, { "name": "Noble Sabatons", @@ -5959,7 +6710,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Noble Tower Shield", @@ -5970,7 +6723,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 83 }, { "name": "Oak Buckler", @@ -5982,7 +6736,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 64 }, { "name": "Oak Greathammer", @@ -5993,7 +6748,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 11 }, { "name": "Oak Mask", @@ -6002,7 +6758,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 36, + "req_int": 36 }, { "name": "Obliterator Bow", @@ -6014,7 +6772,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 163 }, { "name": "Obsidian Dagger", @@ -6025,7 +6784,9 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 34, + "req_int": 34 }, { "name": "Obsidian Greatsword", @@ -6036,7 +6797,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 37, + "req_dex": 37 }, { "name": "Occultist Mantle", @@ -6045,7 +6808,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Ochre Sceptre", @@ -6054,7 +6819,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 40 }, { "name": "Offering Wand", @@ -6066,7 +6832,8 @@ "no_lightning_spell_mods", "onehand", "wand" - ] + ], + "req_int": 68 }, { "name": "Oiled Jacket", @@ -6075,7 +6842,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 43, + "req_int": 43 }, { "name": "Omen Crest Shield", @@ -6087,7 +6856,9 @@ "str_int_armour", "str_int_shield", "vaal_basetype" - ] + ], + "req_str": 30, + "req_int": 30 }, { "name": "Omen Sceptre", @@ -6096,7 +6867,9 @@ "default", "onehand", "sceptre" - ] + ], + "req_str": 12, + "req_int": 25 }, { "name": "Ominous Gloves", @@ -6105,7 +6878,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 56 }, { "name": "Oneiric Ring", @@ -6123,7 +6897,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 87 }, { "name": "Orichalcum Spear", @@ -6133,7 +6908,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 41, + "req_dex": 104 }, { "name": "Ornate Belt", @@ -6154,7 +6931,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 91 }, { "name": "Ornate Cuffs", @@ -6163,7 +6941,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Ornate Gauntlets", @@ -6172,7 +6952,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Ornate Greaves", @@ -6181,7 +6963,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 93 }, { "name": "Ornate Mitts", @@ -6190,7 +6973,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 87 }, { "name": "Ornate Plate", @@ -6199,7 +6983,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Ornate Ringmail", @@ -6208,7 +6993,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 45, + "req_int": 45 }, { "name": "Padded Leggings", @@ -6218,7 +7005,9 @@ "default", "ezomyte_basetype", "str_int_armour" - ] + ], + "req_str": 7, + "req_int": 7 }, { "name": "Painted Buckler", @@ -6230,7 +7019,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 69 }, { "name": "Painted Crest Shield", @@ -6241,7 +7031,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Painted Crossbow", @@ -6253,7 +7045,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Painted Tower Shield", @@ -6265,7 +7059,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 11 }, { "name": "Paragon Greathelm", @@ -6274,7 +7069,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 107 }, { "name": "Paralysing Staff", @@ -6286,7 +7082,8 @@ "no_physical_spell_mods", "staff", "twohand" - ] + ], + "req_int": 92 }, { "name": "Pariah Mask", @@ -6295,7 +7092,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 42, + "req_int": 42 }, { "name": "Parrying Dagger", @@ -6305,7 +7104,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 51, + "req_int": 51 }, { "name": "Patchwork Vest", @@ -6314,7 +7115,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 72 }, { "name": "Pathfinder Coat", @@ -6324,7 +7126,8 @@ "default", "dex_armour", "ezomyte_basetype" - ] + ], + "req_dex": 21 }, { "name": "Pauascale Gloves", @@ -6334,7 +7137,8 @@ "gloves", "int_armour", "karui_basetype" - ] + ], + "req_int": 56 }, { "name": "Pearl Buckler", @@ -6347,7 +7151,8 @@ "dex_shield", "karui_basetype", "shield" - ] + ], + "req_dex": 71 }, { "name": "Pearl Ring", @@ -6375,7 +7180,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 9, + "req_dex": 9 }, { "name": "Pelt Leggings", @@ -6385,7 +7192,9 @@ "default", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 26, + "req_int": 26 }, { "name": "Pelt Mantle", @@ -6395,7 +7204,9 @@ "default", "ezomyte_basetype", "str_int_armour" - ] + ], + "req_str": 12, + "req_int": 12 }, { "name": "Penetrating Quiver", @@ -6426,7 +7237,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 114 }, { "name": "Permafrost Staff", @@ -6434,7 +7246,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 114 }, { "name": "Phalanx Tower Shield", @@ -6445,7 +7258,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 74 }, { "name": "Pict Claw", @@ -6456,7 +7270,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 14 }, { "name": "Piercing Claw", @@ -6466,7 +7281,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 97 }, { "name": "Piercing Crossbow", @@ -6479,7 +7295,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 49, + "req_dex": 49 }, { "name": "Pilgrim Vestments", @@ -6498,7 +7316,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 47, + "req_int": 47 }, { "name": "Pious Sceptre", @@ -6507,7 +7327,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 102 }, { "name": "Plate Belt", @@ -6526,7 +7347,9 @@ "gloves", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 32, + "req_dex": 32 }, { "name": "Plated Buckler", @@ -6539,7 +7362,8 @@ "dex_shield", "ezomyte_basetype", "shield" - ] + ], + "req_dex": 18 }, { "name": "Plated Claw", @@ -6550,7 +7374,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 41 }, { "name": "Plated Greaves", @@ -6559,7 +7384,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 60 }, { "name": "Plated Mace", @@ -6570,7 +7396,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 48 }, { "name": "Plated Mitts", @@ -6579,7 +7406,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 56 }, { "name": "Plated Raiment", @@ -6588,7 +7416,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 92 }, { "name": "Plated Vestments", @@ -6597,7 +7426,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 52, + "req_int": 52 }, { "name": "Plumed Focus", @@ -6607,7 +7438,8 @@ "focus", "int_armour", "vaal_basetype" - ] + ], + "req_int": 52 }, { "name": "Pointed Maul", @@ -6618,7 +7450,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 80 }, { "name": "Polished Bracers", @@ -6628,7 +7461,8 @@ "dex_armour", "gloves", "karui_basetype" - ] + ], + "req_dex": 101 }, { "name": "Polished Targe", @@ -6639,7 +7473,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 42, + "req_dex": 42 }, { "name": "Portent Amulet", @@ -6656,7 +7492,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 96 }, { "name": "Primal Markings", @@ -6665,7 +7502,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Primal Talisman", @@ -6676,7 +7515,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 34, + "req_int": 25 }, { "name": "Primed Quiver", @@ -6695,7 +7536,8 @@ "no_physical_spell_mods", "onehand", "wand" - ] + ], + "req_int": 99 }, { "name": "Prismatic Ring", @@ -6714,7 +7556,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 75, + "req_dex": 30 }, { "name": "Pronged Spear", @@ -6724,7 +7568,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 46, + "req_dex": 115 }, { "name": "Protector Bow", @@ -6735,7 +7581,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 86 }, { "name": "Pyrophyte Staff", @@ -6747,7 +7594,8 @@ "no_physical_spell_mods", "staff", "twohand" - ] + ], + "req_int": 31 }, { "name": "Quartered Crest Shield", @@ -6758,7 +7606,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 46, + "req_int": 46 }, { "name": "Quickslip Shoes", @@ -6767,7 +7617,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 56, + "req_int": 56 }, { "name": "Quilted Vest", @@ -6777,7 +7629,8 @@ "default", "dex_armour", "ezomyte_basetype" - ] + ], + "req_dex": 10 }, { "name": "Rabid Talisman", @@ -6788,7 +7641,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 40, + "req_int": 29 }, { "name": "Raider Plate", @@ -6798,7 +7653,8 @@ "default", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 28 }, { "name": "Rambler Jacket", @@ -6807,7 +7663,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Rampart Tower Shield", @@ -6819,7 +7677,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 42 }, { "name": "Rattling Sceptre", @@ -6836,7 +7695,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 114 }, { "name": "Rawhide Belt", @@ -6865,7 +7725,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Razorglass Claw", @@ -6875,7 +7737,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 80 }, { "name": "Reaching Quarterstaff", @@ -6885,7 +7748,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 63, + "req_int": 26 }, { "name": "Reaping Staff", @@ -6897,7 +7762,8 @@ "no_lightning_spell_mods", "staff", "twohand" - ] + ], + "req_int": 68 }, { "name": "Reaver Glaive", @@ -6907,7 +7773,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_dex": 26 }, { "name": "Recurve Bow", @@ -6919,7 +7787,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 31 }, { "name": "Reefsteel Greaves", @@ -6929,7 +7798,8 @@ "default", "karui_basetype", "str_armour" - ] + ], + "req_str": 60 }, { "name": "Refined Bracers", @@ -6938,7 +7808,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 67 }, { "name": "Refined Breach Ring", @@ -6963,7 +7834,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 114 }, { "name": "Regalia Longsword", @@ -6973,7 +7845,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 53, + "req_dex": 53 }, { "name": "Rending Halberd", @@ -6984,7 +7858,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 47, + "req_dex": 20 }, { "name": "Rending Staff", @@ -6996,7 +7872,8 @@ "no_physical_spell_mods", "staff", "twohand" - ] + ], + "req_int": 60 }, { "name": "Restorative Leg", @@ -7011,7 +7888,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Rhoahide Coat", @@ -7021,7 +7900,8 @@ "default", "dex_armour", "maraketh_basetype" - ] + ], + "req_dex": 37 }, { "name": "Rider Bow", @@ -7032,7 +7912,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 91 }, { "name": "Ridged Buckler", @@ -7045,7 +7926,8 @@ "dex_shield", "maraketh_basetype", "shield" - ] + ], + "req_dex": 33 }, { "name": "Riding Coat", @@ -7054,7 +7936,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 82 }, { "name": "Righteous Cuffs", @@ -7063,7 +7946,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Ring", @@ -7083,7 +7968,9 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_str": 54, + "req_int": 23 }, { "name": "Ring Mail", @@ -7092,7 +7979,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Ringed Buckler", @@ -7105,7 +7994,8 @@ "dex_shield", "shield", "vaal_basetype" - ] + ], + "req_dex": 48 }, { "name": "Ringmail Gauntlets", @@ -7115,7 +8005,9 @@ "ezomyte_basetype", "gloves", "str_dex_armour" - ] + ], + "req_str": 6, + "req_dex": 6 }, { "name": "Rippled Greatsword", @@ -7126,7 +8018,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 24, + "req_dex": 24 }, { "name": "River Raiment", @@ -7135,7 +8029,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 86 }, { "name": "Riveted Mitts", @@ -7145,7 +8040,8 @@ "ezomyte_basetype", "gloves", "str_armour" - ] + ], + "req_str": 16 }, { "name": "Roaring Staff", @@ -7153,7 +8049,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 87 }, { "name": "Roaring Talisman", @@ -7163,7 +8060,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 66, + "req_int": 47 }, { "name": "Robust Crossbow", @@ -7175,7 +8074,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Rogue Armour", @@ -7185,7 +8086,9 @@ "default", "ezomyte_basetype", "str_dex_armour" - ] + ], + "req_str": 13, + "req_dex": 13 }, { "name": "Rope Cuffs", @@ -7195,7 +8098,9 @@ "ezomyte_basetype", "gloves", "str_int_armour" - ] + ], + "req_str": 6, + "req_int": 6 }, { "name": "Rotted Hood", @@ -7204,7 +8109,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 64 }, { "name": "Rough Greaves", @@ -7225,7 +8131,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 91 }, { "name": "Ruby", @@ -7262,7 +8169,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 77 }, { "name": "Ruination Maul", @@ -7272,7 +8180,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Runed Focus", @@ -7282,7 +8191,8 @@ "focus", "int_armour", "karui_basetype" - ] + ], + "req_int": 64 }, { "name": "Runefather's Grasping Mail", @@ -7292,7 +8202,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 57, + "req_dex": 57, + "req_int": 57 }, { "name": "Runeforged Abyssal Cuirass", @@ -7302,7 +8215,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Adherent Cuffs", @@ -7313,7 +8227,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 55, + "req_int": 55 }, { "name": "Runeforged Adherent's Raiment", @@ -7323,7 +8239,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 94 }, { "name": "Runeforged Adorned Gloves", @@ -7333,7 +8250,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 80 }, { "name": "Runeforged Adorned Wraps", @@ -7343,7 +8261,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Runeforged Aegis Buckler", @@ -7356,7 +8276,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 91 }, { "name": "Runeforged Aged Cuffs", @@ -7367,7 +8288,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 13, + "req_int": 13 }, { "name": "Runeforged Altar Robe", @@ -7378,7 +8301,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 65 }, { "name": "Runeforged Ancestor Tower Shield", @@ -7391,7 +8315,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 71 }, { "name": "Runeforged Ancestral Mail", @@ -7402,7 +8327,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Runeforged Ancestral Tiara", @@ -7413,7 +8340,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 115 }, { "name": "Runeforged Anchorite Garb", @@ -7424,7 +8352,9 @@ "dex_int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 31, + "req_int": 31 }, { "name": "Runeforged Ancient Buckler", @@ -7437,7 +8367,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 107 }, { "name": "Runeforged Ancient Cuffs", @@ -7447,7 +8378,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Runeforged Ancient Mitts", @@ -7457,7 +8390,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 67 }, { "name": "Runeforged Antler Focus", @@ -7468,7 +8402,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 17 }, { "name": "Runeforged Apostle Leggings", @@ -7478,7 +8413,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 51, + "req_int": 51 }, { "name": "Runeforged Arcane Raiment", @@ -7488,7 +8425,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Archon Crown", @@ -7498,7 +8436,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Runeforged Armoured Cap", @@ -7508,7 +8448,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 91 }, { "name": "Runeforged Armoured Vest", @@ -7518,7 +8459,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runeforged Array Buckler", @@ -7531,7 +8473,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 78 }, { "name": "Runeforged Arrayed Focus", @@ -7541,7 +8484,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 77 }, { "name": "Runeforged Ascetic Garb", @@ -7552,7 +8496,9 @@ "dex_int_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 45, + "req_int": 45 }, { "name": "Runeforged Assassin Garb", @@ -7562,7 +8508,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Attuned Focus", @@ -7572,7 +8520,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 81 }, { "name": "Runeforged Aureate Targe", @@ -7585,7 +8534,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Runeforged Austere Garb", @@ -7596,7 +8547,9 @@ "dex_int_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Avian Mask", @@ -7606,7 +8559,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 46, + "req_int": 46 }, { "name": "Runeforged Avian Targe", @@ -7618,7 +8573,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Runeforged Barbed Bracers", @@ -7628,7 +8585,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 94 }, { "name": "Runeforged Barbed Spear", @@ -7640,7 +8598,9 @@ "spear", "vaal_basetype", "weapon" - ] + ], + "req_str": 31, + "req_dex": 76 }, { "name": "Runeforged Baroque Gloves", @@ -7650,7 +8610,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 67 }, { "name": "Runeforged Baroque Targe", @@ -7662,7 +8623,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Runeforged Barricade Tower Shield", @@ -7675,7 +8638,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 25 }, { "name": "Runeforged Bastion Sabatons", @@ -7685,7 +8649,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Runeforged Beaded Circlet", @@ -7696,7 +8662,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 25 }, { "name": "Runeforged Blacksteel Crest Shield", @@ -7709,7 +8676,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 63, + "req_int": 63 }, { "name": "Runeforged Blacksteel Gauntlets", @@ -7720,7 +8689,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 55, + "req_dex": 55 }, { "name": "Runeforged Blacksteel Sabatons", @@ -7731,7 +8702,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Runeforged Blacksteel Tower Shield", @@ -7743,7 +8716,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 91 }, { "name": "Runeforged Bladed Shoes", @@ -7753,7 +8727,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 47, + "req_int": 47 }, { "name": "Runeforged Bladeguard Buckler", @@ -7766,7 +8742,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 86 }, { "name": "Runeforged Blazon Crest Shield", @@ -7789,7 +8766,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 47, + "req_dex": 47 }, { "name": "Runeforged Bolstered Mitts", @@ -7800,7 +8779,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 35 }, { "name": "Runeforged Bombard Crossbow", @@ -7814,7 +8794,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Runeforged Bone Raiment", @@ -7825,7 +8807,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 28 }, { "name": "Runeforged Bound Bracers", @@ -7836,7 +8819,8 @@ "gloves", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 22 }, { "name": "Runeforged Bound Cuffs", @@ -7846,7 +8830,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 44, + "req_int": 44 }, { "name": "Runeforged Bound Sandals", @@ -7856,7 +8842,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 86 }, { "name": "Runeforged Braced Sabatons", @@ -7867,7 +8854,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 14, + "req_dex": 14 }, { "name": "Runeforged Braced Tower Shield", @@ -7880,7 +8869,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 19 }, { "name": "Runeforged Brigand Mask", @@ -7890,7 +8880,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Runeforged Brimmed Helm", @@ -7901,7 +8893,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 7, + "req_dex": 7 }, { "name": "Runeforged Bronze Greaves", @@ -7912,7 +8906,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 23 }, { "name": "Runeforged Buckled Wraps", @@ -7923,7 +8918,9 @@ "gloves", "karui_basetype", "runeforged" - ] + ], + "req_dex": 32, + "req_int": 32 }, { "name": "Runeforged Bulwark Greaves", @@ -7933,7 +8930,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 86 }, { "name": "Runeforged Bulwark Tower Shield", @@ -7945,7 +8943,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 77 }, { "name": "Runeforged Burnished Gauntlets", @@ -7955,7 +8954,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Runeforged Cabalist Helm", @@ -7965,7 +8966,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 42, + "req_dex": 42 }, { "name": "Runeforged Carved Greaves", @@ -7975,7 +8978,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 78 }, { "name": "Runeforged Carved Targe", @@ -7987,7 +8991,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 40, + "req_dex": 40 }, { "name": "Runeforged Cavalry Boots", @@ -7997,7 +9003,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 93 }, { "name": "Runeforged Ceremonial Robe", @@ -8007,7 +9014,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 98 }, { "name": "Runeforged Chain Mail", @@ -8029,7 +9037,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 38 }, { "name": "Runeforged Champion Cuirass", @@ -8039,7 +9048,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 92 }, { "name": "Runeforged Champion Helm", @@ -8049,7 +9059,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Runeforged Changeling Talisman", @@ -8061,7 +9073,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 45, + "req_int": 32 }, { "name": "Runeforged Charmed Shoes", @@ -8071,7 +9085,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 51, + "req_int": 51 }, { "name": "Runeforged Chieftain Cuirass", @@ -8082,7 +9098,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Runeforged Chiseled Targe", @@ -8095,7 +9112,9 @@ "str_dex_armour", "str_dex_shield", "vaal_basetype" - ] + ], + "req_str": 27, + "req_dex": 27 }, { "name": "Runeforged Cinched Boots", @@ -8105,7 +9124,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 86 }, { "name": "Runeforged Clandestine Jacket", @@ -8115,7 +9135,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 46, + "req_int": 46 }, { "name": "Runeforged Cleric Vestments", @@ -8126,7 +9148,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Runeforged Cloaked Mail", @@ -8137,7 +9161,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 25, + "req_dex": 25 }, { "name": "Runeforged Closed Helm", @@ -8148,7 +9174,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Runeforged Colosseum Plate", @@ -8158,7 +9186,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 83 }, { "name": "Runeforged Commander Gauntlets", @@ -8168,7 +9197,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Runeforged Commander Greathelm", @@ -8179,7 +9210,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 64 }, { "name": "Runeforged Composite Bow", @@ -8192,7 +9224,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 97 }, { "name": "Runeforged Conjurer Mantle", @@ -8202,7 +9235,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runeforged Conqueror Plate", @@ -8212,7 +9247,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Corsair Cap", @@ -8223,7 +9259,8 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 64 }, { "name": "Runeforged Corsair Coat", @@ -8234,7 +9271,8 @@ "dex_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runeforged Corsair Vest", @@ -8245,7 +9283,8 @@ "dex_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 72 }, { "name": "Runeforged Corvus Mantle", @@ -8255,7 +9294,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runeforged Covered Sabatons", @@ -8266,7 +9307,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Runeforged Covert Hood", @@ -8276,7 +9319,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 79 }, { "name": "Runeforged Cowled Helm", @@ -8287,7 +9331,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 22, + "req_dex": 22 }, { "name": "Runeforged Crescent Targe", @@ -8300,7 +9346,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 22, + "req_dex": 22 }, { "name": "Runeforged Crucible Tower Shield", @@ -8313,7 +9361,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 64 }, { "name": "Runeforged Crude Bow", @@ -8326,7 +9375,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 97 }, { "name": "Runeforged Crumbling Maul", @@ -8338,7 +9388,8 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 97 }, { "name": "Runeforged Cryptic Crown", @@ -8349,7 +9400,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 63, + "req_int": 63 }, { "name": "Runeforged Cryptic Helm", @@ -8359,7 +9412,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Runeforged Cryptic Leggings", @@ -8370,7 +9425,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Runeforged Crystal Focus", @@ -8381,7 +9438,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 38 }, { "name": "Runeforged Cultist Crown", @@ -8392,7 +9450,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 15, + "req_int": 15 }, { "name": "Runeforged Cultist Focus", @@ -8402,7 +9462,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 83 }, { "name": "Runeforged Cultist Gauntlets", @@ -8412,7 +9473,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 52, + "req_dex": 52 }, { "name": "Runeforged Cultist Greathammer", @@ -8424,7 +9487,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 72 }, { "name": "Runeforged Daggerfoot Shoes", @@ -8435,7 +9499,9 @@ "dex_int_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 59, + "req_int": 59 }, { "name": "Runeforged Dastard Armour", @@ -8445,7 +9511,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Death Mail", @@ -8455,7 +9523,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Death Mantle", @@ -8465,7 +9535,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runeforged Death Mask", @@ -8475,7 +9547,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Runeforged Decorated Helm", @@ -8485,7 +9559,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Runeforged Deerstalker Hood", @@ -8495,7 +9571,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 83 }, { "name": "Runeforged Defiant Tower Shield", @@ -8507,7 +9584,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 82 }, { "name": "Runeforged Deified Crest Shield", @@ -8519,7 +9597,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Runeforged Dekharan Crest Shield", @@ -8531,7 +9611,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 42, + "req_int": 42 }, { "name": "Runeforged Desert Buckler", @@ -8545,7 +9627,8 @@ "karui_basetype", "runeforged", "shield" - ] + ], + "req_dex": 115 }, { "name": "Runeforged Desert Cap", @@ -8555,7 +9638,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 99 }, { "name": "Runeforged Detailed Mitts", @@ -8566,7 +9650,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 56 }, { "name": "Runeforged Devout Garb", @@ -8576,7 +9661,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Divine Crown", @@ -8586,7 +9673,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Runeforged Doubled Gauntlets", @@ -8597,7 +9686,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 24, + "req_dex": 24 }, { "name": "Runeforged Dragonscale Boots", @@ -8607,7 +9698,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 101 }, { "name": "Runeforged Drakeskin Boots", @@ -8618,7 +9710,8 @@ "dex_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 108 }, { "name": "Runeforged Druidic Crown", @@ -8628,7 +9721,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Runeforged Druidic Focus", @@ -8638,7 +9733,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runeforged Dualstring Bow", @@ -8651,7 +9747,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 97 }, { "name": "Runeforged Dunerunner Sandals", @@ -8661,7 +9758,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 78 }, { "name": "Runeforged Dyad Crossbow", @@ -8675,7 +9773,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 40, + "req_dex": 40 }, { "name": "Runeforged Edged Buckler", @@ -8689,7 +9789,8 @@ "runeforged", "shield", "vaal_basetype" - ] + ], + "req_dex": 56 }, { "name": "Runeforged Effigial Tower Shield", @@ -8702,7 +9803,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 32 }, { "name": "Runeforged Elaborate Sandals", @@ -8712,7 +9814,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 86 }, { "name": "Runeforged Elegant Focus", @@ -8722,7 +9825,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 74 }, { "name": "Runeforged Elegant Greathelm", @@ -8732,7 +9836,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 77 }, { "name": "Runeforged Elegant Greaves", @@ -8742,7 +9847,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 72 }, { "name": "Runeforged Elegant Plate", @@ -8752,7 +9858,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 86 }, { "name": "Runeforged Elegant Slippers", @@ -8762,7 +9869,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 72 }, { "name": "Runeforged Elegant Wraps", @@ -8772,7 +9880,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 48, + "req_int": 48 }, { "name": "Runeforged Elementalist Robe", @@ -8783,7 +9893,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 72 }, { "name": "Runeforged Elite Greathelm", @@ -8794,7 +9905,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 48 }, { "name": "Runeforged Emblem Crest Shield", @@ -8807,7 +9919,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 15, + "req_int": 15 }, { "name": "Runeforged Embossed Boots", @@ -8818,7 +9932,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 23 }, { "name": "Runeforged Embroidered Boots", @@ -8828,7 +9943,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 86 }, { "name": "Runeforged Embroidered Gloves", @@ -8838,7 +9954,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 64 }, { "name": "Runeforged Empyreal Crest Shield", @@ -8850,7 +9967,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 45, + "req_int": 45 }, { "name": "Runeforged Engraved Bracers", @@ -8860,7 +9979,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 80 }, { "name": "Runeforged Engraved Focus", @@ -8871,7 +9991,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 25 }, { "name": "Runeforged Enlightened Robe", @@ -8881,7 +10002,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Explorer Armour", @@ -8892,7 +10014,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Runeforged Exquisite Vest", @@ -8902,7 +10026,9 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 85, + "req_int": 85 }, { "name": "Runeforged Face Mask", @@ -8913,7 +10039,9 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 10, + "req_int": 10 }, { "name": "Runeforged Faithful Leggings", @@ -8923,7 +10051,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 47, + "req_int": 47 }, { "name": "Runeforged Falconer's Jacket", @@ -8933,7 +10063,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Familial Talisman", @@ -8945,7 +10077,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 45, + "req_int": 32 }, { "name": "Runeforged Faridun Mask", @@ -8955,7 +10089,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 54, + "req_int": 54 }, { "name": "Runeforged Feathered Mitts", @@ -8965,7 +10101,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 73 }, { "name": "Runeforged Feathered Raiment", @@ -8976,7 +10113,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Feathered Robe", @@ -8987,7 +10125,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 11 }, { "name": "Runeforged Feathered Sandals", @@ -8998,7 +10137,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 45 }, { "name": "Runeforged Feathered Targe", @@ -9011,7 +10151,9 @@ "str_dex_armour", "str_dex_shield", "vaal_basetype" - ] + ], + "req_str": 30, + "req_dex": 30 }, { "name": "Runeforged Feathered Tiara", @@ -9022,7 +10164,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 48 }, { "name": "Runeforged Felled Greatclub", @@ -9034,7 +10177,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 68 }, { "name": "Runeforged Felt Cap", @@ -9045,7 +10189,8 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 17 }, { "name": "Runeforged Fierce Greathelm", @@ -9056,7 +10201,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 73 }, { "name": "Runeforged Fine Bracers", @@ -9067,7 +10213,8 @@ "gloves", "karui_basetype", "runeforged" - ] + ], + "req_dex": 56 }, { "name": "Runeforged Firm Bracers", @@ -9078,7 +10225,8 @@ "ezomyte_basetype", "gloves", "runeforged" - ] + ], + "req_dex": 16 }, { "name": "Runeforged Fists of Stone", @@ -9099,7 +10247,8 @@ "dex_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 60 }, { "name": "Runeforged Flax Sandals", @@ -9110,7 +10259,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 60 }, { "name": "Runeforged Flexile Sabatons", @@ -9120,7 +10270,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Runeforged Flowing Raiment", @@ -9130,7 +10282,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Forge Maul", @@ -9142,7 +10295,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 72 }, { "name": "Runeforged Forked Spear", @@ -9154,7 +10308,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 31, + "req_dex": 76 }, { "name": "Runeforged Fortress Sabatons", @@ -9164,7 +10320,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 56, + "req_dex": 56 }, { "name": "Runeforged Fortress Tower Shield", @@ -9176,7 +10334,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 99 }, { "name": "Runeforged Frayed Shoes", @@ -9187,7 +10346,9 @@ "dex_int_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 7, + "req_int": 7 }, { "name": "Runeforged Freebooter Cap", @@ -9198,7 +10359,8 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 115 }, { "name": "Runeforged Full Plate", @@ -9209,7 +10371,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 54 }, { "name": "Runeforged Fur Plate", @@ -9220,7 +10383,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 61 }, { "name": "Runeforged Furtive Wraps", @@ -9230,7 +10394,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 36, + "req_int": 36 }, { "name": "Runeforged Gallant Helm", @@ -9240,7 +10406,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Runeforged Garment", @@ -9261,7 +10429,9 @@ "ezomyte_basetype", "gloves", "runeforged" - ] + ], + "req_dex": 6, + "req_int": 6 }, { "name": "Runeforged Gilded Vestments", @@ -9271,7 +10441,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 46, + "req_int": 46 }, { "name": "Runeforged Gilt Leggings", @@ -9281,7 +10453,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Runeforged Gladiator Armour", @@ -9291,7 +10465,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Runeforged Gladiatorial Helm", @@ -9302,7 +10478,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Runeforged Gleaming Cuffs", @@ -9312,7 +10490,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 52, + "req_int": 52 }, { "name": "Runeforged Glorious Plate", @@ -9322,7 +10502,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Glowering Crest Shield", @@ -9334,7 +10515,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Runeforged Gold Circlet", @@ -9345,7 +10528,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 58 }, { "name": "Runeforged Gold Gloves", @@ -9355,7 +10539,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 73 }, { "name": "Runeforged Goldcast Cuffs", @@ -9366,7 +10551,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 24, + "req_int": 24 }, { "name": "Runeforged Golden Mail", @@ -9376,7 +10563,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Runeforged Golden Targe", @@ -9389,7 +10578,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Runeforged Goldworked Tower Shield", @@ -9401,7 +10592,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 86 }, { "name": "Runeforged Goliath Greathelm", @@ -9411,7 +10603,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 81 }, { "name": "Runeforged Grand Bracers", @@ -9421,7 +10614,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 87 }, { "name": "Runeforged Grand Cuisses", @@ -9431,7 +10625,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 34, + "req_dex": 34, + "req_int": 34 }, { "name": "Runeforged Grand Manchettes", @@ -9441,7 +10638,10 @@ "gloves", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 32, + "req_dex": 32, + "req_int": 32 }, { "name": "Runeforged Grand Mitts", @@ -9451,7 +10651,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Runeforged Grand Regalia", @@ -9461,7 +10662,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 41, + "req_dex": 41, + "req_int": 41 }, { "name": "Runeforged Grand Targe", @@ -9473,7 +10677,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Runeforged Grand Visage", @@ -9483,7 +10689,10 @@ "helmet", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 36, + "req_dex": 36, + "req_int": 36 }, { "name": "Runeforged Grasping Mail", @@ -9493,7 +10702,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 41, + "req_dex": 41, + "req_int": 41 }, { "name": "Runeforged Grim Gloves", @@ -9503,7 +10715,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 80 }, { "name": "Runeforged Grinning Mask", @@ -9514,7 +10727,9 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 63, + "req_int": 63 }, { "name": "Runeforged Guarded Helm", @@ -9525,7 +10740,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 11, + "req_dex": 11 }, { "name": "Runeforged Guardian Greathelm", @@ -9535,7 +10752,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 91 }, { "name": "Runeforged Gutspike Buckler", @@ -9548,7 +10766,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 99 }, { "name": "Runeforged Hallowed Crown", @@ -9558,7 +10777,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 42, + "req_int": 42 }, { "name": "Runeforged Hallowed Focus", @@ -9568,7 +10789,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 86 }, { "name": "Runeforged Hardened Bracers", @@ -9578,7 +10800,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 64 }, { "name": "Runeforged Hardleather Coat", @@ -9588,7 +10811,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 89 }, { "name": "Runeforged Hardwood Spear", @@ -9600,7 +10824,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 23, + "req_dex": 56 }, { "name": "Runeforged Hardwood Targe", @@ -9623,7 +10849,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 52, + "req_int": 52 }, { "name": "Runeforged Havoc Raiment", @@ -9633,7 +10861,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Hawker's Jacket", @@ -9643,7 +10872,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 54, + "req_int": 54 }, { "name": "Runeforged Heartcarver Mantle", @@ -9653,7 +10884,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 54, + "req_int": 54 }, { "name": "Runeforged Heavy Crown", @@ -9664,7 +10897,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 27, + "req_int": 27 }, { "name": "Runeforged Heavy Plate", @@ -9674,7 +10909,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 94 }, { "name": "Runeforged Heraldric Tower Shield", @@ -9687,7 +10923,8 @@ "str_armour", "str_shield", "vaal_basetype" - ] + ], + "req_str": 48 }, { "name": "Runeforged Hermit Garb", @@ -9708,7 +10945,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Hewn Mask", @@ -9719,7 +10958,9 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 7, + "req_int": 7 }, { "name": "Runeforged Hexer's Robe", @@ -9730,7 +10971,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 21 }, { "name": "Runeforged Hooded Mask", @@ -9741,7 +10983,9 @@ "helmet", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 15, + "req_int": 15 }, { "name": "Runeforged Horned Crown", @@ -9752,7 +10996,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 10, + "req_int": 10 }, { "name": "Runeforged Hunter Hood", @@ -9763,7 +11009,8 @@ "helmet", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 48 }, { "name": "Runeforged Hunting Shoes", @@ -9774,7 +11021,9 @@ "dex_int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 26, + "req_int": 26 }, { "name": "Runeforged Hunting Spear", @@ -9786,7 +11035,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 23, + "req_dex": 54 }, { "name": "Runeforged Imperial Greathelm", @@ -9797,7 +11048,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 115 }, { "name": "Runeforged Imperial Robe", @@ -9807,7 +11059,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 83 }, { "name": "Runeforged Inquisitor Crown", @@ -9817,7 +11070,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 46, + "req_int": 46 }, { "name": "Runeforged Intricate Crest Shield", @@ -9829,7 +11084,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Runeforged Intricate Gloves", @@ -9840,7 +11097,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 42 }, { "name": "Runeforged Iron Buckler", @@ -9854,7 +11112,8 @@ "maraketh_basetype", "runeforged", "shield" - ] + ], + "req_dex": 25 }, { "name": "Runeforged Iron Crown", @@ -9865,7 +11124,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 7, + "req_int": 7 }, { "name": "Runeforged Iron Cuirass", @@ -9876,7 +11137,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 21 }, { "name": "Runeforged Iron Greaves", @@ -9887,7 +11149,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 17 }, { "name": "Runeforged Ironclad Vestments", @@ -9898,7 +11161,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Runeforged Ironhead Spear", @@ -9910,7 +11175,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 23, + "req_dex": 54 }, { "name": "Runeforged Itinerant Jacket", @@ -9920,7 +11187,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 48, + "req_int": 48 }, { "name": "Runeforged Jade Tiara", @@ -9931,7 +11200,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 71 }, { "name": "Runeforged Jewelled Gloves", @@ -9942,7 +11212,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 34 }, { "name": "Runeforged Jingling Buckler", @@ -9955,7 +11226,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 83 }, { "name": "Runeforged Jingling Crest Shield", @@ -9968,7 +11240,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 23, + "req_int": 23 }, { "name": "Runeforged Juggernaut Plate", @@ -9979,7 +11253,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 72 }, { "name": "Runeforged Jungle Tiara", @@ -9989,7 +11264,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 83 }, { "name": "Runeforged Kalguuran Cuffs", @@ -10000,7 +11276,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 32, + "req_int": 32 }, { "name": "Runeforged Kalguuran Forgehammer", @@ -10021,7 +11299,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 107 }, { "name": "Runeforged Keth Raiment", @@ -10032,7 +11311,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 47 }, { "name": "Runeforged Knight Armour", @@ -10043,7 +11323,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Runeforged Knightly Mitts", @@ -10053,7 +11335,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Runeforged Lace Hood", @@ -10064,7 +11347,8 @@ "helmet", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 54 }, { "name": "Runeforged Laced Boots", @@ -10075,7 +11359,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 17 }, { "name": "Runeforged Lamellar Mail", @@ -10085,7 +11370,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 46, + "req_dex": 46 }, { "name": "Runeforged Laminate Buckler", @@ -10099,7 +11386,8 @@ "karui_basetype", "runeforged", "shield" - ] + ], + "req_dex": 64 }, { "name": "Runeforged Lattice Sandals", @@ -10110,7 +11398,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 23 }, { "name": "Runeforged Lavish Crown", @@ -10120,7 +11409,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Runeforged Layered Gauntlets", @@ -10131,7 +11422,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 13, + "req_dex": 13 }, { "name": "Runeforged Layered Vest", @@ -10141,7 +11434,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 86 }, { "name": "Runeforged Leaden Greathammer", @@ -10153,7 +11447,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 60, + "req_int": 50 }, { "name": "Runeforged Leather Buckler", @@ -10189,7 +11485,8 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 71 }, { "name": "Runeforged Leatherplate Boots", @@ -10199,7 +11496,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 69 }, { "name": "Runeforged Leyline Focus", @@ -10209,7 +11507,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 99 }, { "name": "Runeforged Linen Wraps", @@ -10220,7 +11519,9 @@ "gloves", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 13, + "req_int": 13 }, { "name": "Runeforged Lizardscale Boots", @@ -10231,7 +11532,8 @@ "dex_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 45 }, { "name": "Runeforged Lizardscale Coat", @@ -10241,7 +11543,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 98 }, { "name": "Runeforged Luxurious Slippers", @@ -10251,7 +11554,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 93 }, { "name": "Runeforged Magus Focus", @@ -10261,7 +11565,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runeforged Magus Tiara", @@ -10271,7 +11576,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runeforged Mail Coat", @@ -10281,7 +11587,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runeforged Mail Sabatons", @@ -10292,7 +11599,9 @@ "ezomyte_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 7, + "req_dex": 7 }, { "name": "Runeforged Mail Vestments", @@ -10303,7 +11612,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 17, + "req_int": 17 }, { "name": "Runeforged Makeshift Crossbow", @@ -10317,7 +11628,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 39, + "req_dex": 39 }, { "name": "Runeforged Mammoth Targe", @@ -10329,7 +11642,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Runeforged Mantled Mail", @@ -10339,7 +11654,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 48, + "req_dex": 48 }, { "name": "Runeforged Marabout Garb", @@ -10350,7 +11667,9 @@ "dex_int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 17, + "req_int": 17 }, { "name": "Runeforged Maraketh Cuirass", @@ -10361,7 +11680,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 34 }, { "name": "Runeforged Marching Mace", @@ -10372,7 +11692,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 114 }, { "name": "Runeforged Martyr Crown", @@ -10383,7 +11704,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 23, + "req_int": 23 }, { "name": "Runeforged Masked Greathelm", @@ -10393,7 +11716,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 99 }, { "name": "Runeforged Massive Mitts", @@ -10404,7 +11728,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 101 }, { "name": "Runeforged Monastic Garb", @@ -10414,7 +11739,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Runeforged Monument Greaves", @@ -10424,7 +11751,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 69 }, { "name": "Runeforged Morning Star", @@ -10435,7 +11763,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 114 }, { "name": "Runeforged Mosaic Targe", @@ -10448,7 +11777,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Runeforged Moulded Mitts", @@ -10459,7 +11790,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 42 }, { "name": "Runeforged Mystic Raiment", @@ -10470,7 +11802,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 78 }, { "name": "Runeforged Nettle Talisman", @@ -10482,7 +11815,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 45, + "req_int": 32 }, { "name": "Runeforged Noble Circlet", @@ -10492,7 +11827,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 74 }, { "name": "Runeforged Noble Greathelm", @@ -10502,7 +11838,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 83 }, { "name": "Runeforged Noble Sabatons", @@ -10512,7 +11849,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 51, + "req_dex": 51 }, { "name": "Runeforged Noble Tower Shield", @@ -10524,7 +11863,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 83 }, { "name": "Runeforged Oak Greathammer", @@ -10536,7 +11876,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 68 }, { "name": "Runeforged Occultist Mantle", @@ -10546,7 +11887,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Runeforged Omen Crest Shield", @@ -10559,7 +11902,9 @@ "str_int_armour", "str_int_shield", "vaal_basetype" - ] + ], + "req_str": 30, + "req_int": 30 }, { "name": "Runeforged Opulent Gloves", @@ -10569,7 +11914,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 87 }, { "name": "Runeforged Ornate Buckler", @@ -10582,7 +11928,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 91 }, { "name": "Runeforged Ornate Cuffs", @@ -10592,7 +11939,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Runeforged Ornate Gauntlets", @@ -10602,7 +11951,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Runeforged Ornate Greaves", @@ -10612,7 +11963,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 93 }, { "name": "Runeforged Ornate Mitts", @@ -10622,7 +11974,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 87 }, { "name": "Runeforged Ornate Plate", @@ -10632,7 +11985,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Padded Leggings", @@ -10643,7 +11997,9 @@ "ezomyte_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 7, + "req_int": 7 }, { "name": "Runeforged Painted Tower Shield", @@ -10656,7 +12012,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 11 }, { "name": "Runeforged Paragon Greathelm", @@ -10666,7 +12023,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 107 }, { "name": "Runeforged Pariah Mask", @@ -10676,7 +12034,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 42, + "req_int": 42 }, { "name": "Runeforged Pathfinder Coat", @@ -10687,7 +12047,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 21 }, { "name": "Runeforged Pauascale Gloves", @@ -10698,7 +12059,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 56 }, { "name": "Runeforged Pearl Buckler", @@ -10712,7 +12074,8 @@ "karui_basetype", "runeforged", "shield" - ] + ], + "req_dex": 71 }, { "name": "Runeforged Pelage Targe", @@ -10725,7 +12088,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 9, + "req_dex": 9 }, { "name": "Runeforged Pelt Leggings", @@ -10736,7 +12101,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 26, + "req_int": 26 }, { "name": "Runeforged Pelt Mantle", @@ -10747,7 +12114,9 @@ "ezomyte_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 12, + "req_int": 12 }, { "name": "Runeforged Phalanx Tower Shield", @@ -10759,7 +12128,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 74 }, { "name": "Runeforged Pilgrim Vestments", @@ -10780,7 +12150,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 47, + "req_int": 47 }, { "name": "Runeforged Plate Gauntlets", @@ -10791,7 +12163,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 32, + "req_dex": 32 }, { "name": "Runeforged Plated Buckler", @@ -10805,7 +12179,8 @@ "ezomyte_basetype", "runeforged", "shield" - ] + ], + "req_dex": 18 }, { "name": "Runeforged Plated Raiment", @@ -10815,7 +12190,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 92 }, { "name": "Runeforged Plated Vestments", @@ -10825,7 +12201,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 52, + "req_int": 52 }, { "name": "Runeforged Plumed Focus", @@ -10836,7 +12214,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 52 }, { "name": "Runeforged Pointed Maul", @@ -10848,7 +12227,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 114 }, { "name": "Runeforged Polished Bracers", @@ -10859,7 +12239,8 @@ "gloves", "karui_basetype", "runeforged" - ] + ], + "req_dex": 101 }, { "name": "Runeforged Polished Targe", @@ -10871,7 +12252,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 42, + "req_dex": 42 }, { "name": "Runeforged Primal Markings", @@ -10881,7 +12264,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Quartered Crest Shield", @@ -10893,7 +12278,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 46, + "req_int": 46 }, { "name": "Runeforged Quickslip Shoes", @@ -10903,7 +12290,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 56, + "req_int": 56 }, { "name": "Runeforged Quilted Vest", @@ -10914,7 +12303,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 10 }, { "name": "Runeforged Raider Plate", @@ -10925,7 +12315,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 28 }, { "name": "Runeforged Rambler Jacket", @@ -10935,7 +12326,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Rampart Tower Shield", @@ -10948,7 +12341,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 42 }, { "name": "Runeforged Rawhide Boots", @@ -10972,7 +12366,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 72 }, { "name": "Runeforged Reefsteel Greaves", @@ -10983,7 +12378,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 60 }, { "name": "Runeforged Refined Bracers", @@ -10993,7 +12389,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 67 }, { "name": "Runeforged Revered Vestments", @@ -11003,7 +12400,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runeforged Rhoahide Coat", @@ -11014,7 +12413,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 37 }, { "name": "Runeforged Ridged Buckler", @@ -11028,7 +12428,8 @@ "maraketh_basetype", "runeforged", "shield" - ] + ], + "req_dex": 33 }, { "name": "Runeforged Righteous Cuffs", @@ -11038,7 +12439,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Runeforged Ringed Buckler", @@ -11052,7 +12455,8 @@ "runeforged", "shield", "vaal_basetype" - ] + ], + "req_dex": 48 }, { "name": "Runeforged Ringmail Gauntlets", @@ -11063,7 +12467,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 6, + "req_dex": 6 }, { "name": "Runeforged River Raiment", @@ -11073,7 +12479,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 86 }, { "name": "Runeforged Riveted Mitts", @@ -11084,7 +12491,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 16 }, { "name": "Runeforged Rogue Armour", @@ -11095,7 +12503,9 @@ "ezomyte_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 13, + "req_dex": 13 }, { "name": "Runeforged Rope Cuffs", @@ -11106,7 +12516,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 6, + "req_int": 6 }, { "name": "Runeforged Rough Greaves", @@ -11129,7 +12541,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 91 }, { "name": "Runeforged Runed Focus", @@ -11140,7 +12553,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 64 }, { "name": "Runeforged Runner Vest", @@ -11150,7 +12564,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 94 }, { "name": "Runeforged Rusted Cuirass", @@ -11182,7 +12597,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Sacred Focus", @@ -11192,7 +12608,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 107 }, { "name": "Runeforged Sacrificial Mantle", @@ -11203,7 +12620,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 33, + "req_int": 33 }, { "name": "Runeforged Sacrificial Regalia", @@ -11213,7 +12632,9 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 80, + "req_int": 80 }, { "name": "Runeforged Saintly Crown", @@ -11223,7 +12644,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 54, + "req_int": 54 }, { "name": "Runeforged Sandsworn Sandals", @@ -11233,7 +12656,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 101 }, { "name": "Runeforged Sandsworn Tiara", @@ -11243,7 +12667,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 77 }, { "name": "Runeforged Scale Mail", @@ -11254,7 +12679,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Runeforged Scalper's Jacket", @@ -11265,7 +12692,9 @@ "dex_int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 35, + "req_int": 35 }, { "name": "Runeforged Scoundrel Jacket", @@ -11276,7 +12705,9 @@ "dex_int_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Runeforged Scout's Vest", @@ -11287,7 +12718,8 @@ "dex_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 54 }, { "name": "Runeforged Seastorm Mantle", @@ -11298,7 +12730,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runeforged Sectarian Crest Shield", @@ -11311,7 +12745,9 @@ "str_int_armour", "str_int_shield", "vaal_basetype" - ] + ], + "req_str": 27, + "req_int": 27 }, { "name": "Runeforged Sectioned Bracers", @@ -11322,7 +12758,8 @@ "gloves", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 36 }, { "name": "Runeforged Secured Leggings", @@ -11333,7 +12770,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 14, + "req_int": 14 }, { "name": "Runeforged Secured Wraps", @@ -11344,7 +12783,9 @@ "gloves", "karui_basetype", "runeforged" - ] + ], + "req_dex": 55, + "req_int": 55 }, { "name": "Runeforged Seer Crest Shield", @@ -11357,7 +12798,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 39, + "req_int": 39 }, { "name": "Runeforged Sekhema Sandals", @@ -11368,7 +12811,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 108 }, { "name": "Runeforged Sekheman Crest Shield", @@ -11380,7 +12824,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 54, + "req_int": 54 }, { "name": "Runeforged Sentinel Greathelm", @@ -11390,7 +12836,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 74 }, { "name": "Runeforged Serpentscale Boots", @@ -11400,7 +12847,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 78 }, { "name": "Runeforged Serpentscale Coat", @@ -11411,7 +12859,8 @@ "dex_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 59 }, { "name": "Runeforged Shabby Hood", @@ -11432,7 +12881,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Runeforged Shaman Mantle", @@ -11443,7 +12894,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 26, + "req_int": 26 }, { "name": "Runeforged Shamanistic Leggings", @@ -11453,7 +12906,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 44, + "req_int": 44 }, { "name": "Runeforged Shielded Helm", @@ -11464,7 +12919,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 27, + "req_dex": 27 }, { "name": "Runeforged Shortbow", @@ -11477,7 +12934,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 97 }, { "name": "Runeforged Shrouded Mail", @@ -11487,7 +12945,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Shrouded Vest", @@ -11498,7 +12958,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 28 }, { "name": "Runeforged Sigil Crest Shield", @@ -11511,7 +12972,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 8, + "req_int": 8 }, { "name": "Runeforged Signet Cuffs", @@ -11521,7 +12984,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 44, + "req_int": 44 }, { "name": "Runeforged Silk Robe", @@ -11532,7 +12997,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 37 }, { "name": "Runeforged Silk Slippers", @@ -11543,7 +13009,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 38 }, { "name": "Runeforged Sirenscale Gloves", @@ -11554,7 +13021,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 101 }, { "name": "Runeforged Skycrown Tiara", @@ -11564,7 +13032,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runeforged Slayer Armour", @@ -11574,7 +13043,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Sleek Jacket", @@ -11584,7 +13055,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Slim Mace", @@ -11596,7 +13069,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 68 }, { "name": "Runeforged Slipstrike Vest", @@ -11606,7 +13080,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runeforged Smuggler Coat", @@ -11617,7 +13092,8 @@ "dex_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 82 }, { "name": "Runeforged Soaring Mask", @@ -11627,7 +13103,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 59, + "req_int": 59 }, { "name": "Runeforged Soaring Targe", @@ -11639,7 +13117,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Runeforged Soldier Cuirass", @@ -11649,7 +13129,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Soldier Greathelm", @@ -11660,7 +13141,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 19 }, { "name": "Runeforged Solid Mask", @@ -11671,7 +13153,9 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 36, + "req_int": 36 }, { "name": "Runeforged Sombre Gloves", @@ -11682,7 +13166,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 17 }, { "name": "Runeforged Sorcerous Tiara", @@ -11692,7 +13177,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 99 }, { "name": "Runeforged Spiked Bracers", @@ -11702,7 +13188,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 73 }, { "name": "Runeforged Spiked Buckler", @@ -11716,7 +13203,8 @@ "maraketh_basetype", "runeforged", "shield" - ] + ], + "req_dex": 38 }, { "name": "Runeforged Spiked Club", @@ -11728,7 +13216,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 72 }, { "name": "Runeforged Spikeward Buckler", @@ -11741,7 +13230,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 77 }, { "name": "Runeforged Spined Bracers", @@ -11752,7 +13242,8 @@ "gloves", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 42 }, { "name": "Runeforged Spiral Wraps", @@ -11763,7 +13254,9 @@ "gloves", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 24, + "req_int": 24 }, { "name": "Runeforged Spired Greathelm", @@ -11774,7 +13267,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 40 }, { "name": "Runeforged Spiritbone Crown", @@ -11785,7 +13279,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Runeforged Splintered Tower Shield", @@ -11809,7 +13305,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 26, + "req_dex": 26 }, { "name": "Runeforged Stalking Bracers", @@ -11819,7 +13317,8 @@ "dex_armour", "gloves", "runeforged" - ] + ], + "req_dex": 80 }, { "name": "Runeforged Steel Plate", @@ -11830,7 +13329,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 45 }, { "name": "Runeforged Steelmail Gauntlets", @@ -11840,7 +13340,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Runeforged Steelpoint Shoes", @@ -11851,7 +13353,9 @@ "dex_int_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 34, + "req_int": 34 }, { "name": "Runeforged Steeltoe Boots", @@ -11862,7 +13366,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 39 }, { "name": "Runeforged Stitched Gloves", @@ -11873,7 +13378,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 22 }, { "name": "Runeforged Stocky Mitts", @@ -11896,7 +13402,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Runeforged Stone Cuirass", @@ -11906,7 +13414,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 98 }, { "name": "Runeforged Stone Greaves", @@ -11917,7 +13426,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 45 }, { "name": "Runeforged Stone Targe", @@ -11929,7 +13439,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 46, + "req_dex": 46 }, { "name": "Runeforged Stone Tower Shield", @@ -11942,7 +13454,8 @@ "str_armour", "str_shield", "vaal_basetype" - ] + ], + "req_str": 52 }, { "name": "Runeforged Stratified Targe", @@ -11954,7 +13467,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 37, + "req_dex": 37 }, { "name": "Runeforged Straw Sandals", @@ -11975,7 +13490,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 83 }, { "name": "Runeforged Studded Boots", @@ -11985,7 +13501,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 72 }, { "name": "Runeforged Studded Greatclub", @@ -11997,7 +13514,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 68 }, { "name": "Runeforged Studded Sandals", @@ -12007,7 +13525,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 69 }, { "name": "Runeforged Studded Targe", @@ -12020,7 +13539,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 15, + "req_dex": 15 }, { "name": "Runeforged Studded Vest", @@ -12031,7 +13552,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 43 }, { "name": "Runeforged Sturdy Crossbow", @@ -12045,7 +13567,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 39, + "req_dex": 39 }, { "name": "Runeforged Suede Bracers", @@ -12067,7 +13591,8 @@ "helmet", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 38 }, { "name": "Runeforged Swiftstalker Coat", @@ -12077,7 +13602,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runeforged Tasalian Focus", @@ -12088,7 +13614,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 115 }, { "name": "Runeforged Tasalian Greaves", @@ -12099,7 +13626,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 108 }, { "name": "Runeforged Tattered Robe", @@ -12123,7 +13651,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 115 }, { "name": "Runeforged Tempered Mitts", @@ -12134,7 +13663,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 22 }, { "name": "Runeforged Temple Maul", @@ -12146,7 +13676,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 76, + "req_dex": 40 }, { "name": "Runeforged Tense Crossbow", @@ -12160,7 +13692,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 39, + "req_dex": 39 }, { "name": "Runeforged Thane Mail", @@ -12171,7 +13705,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Threaded Shoes", @@ -12182,7 +13718,9 @@ "dex_int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 14, + "req_int": 14 }, { "name": "Runeforged Tideseer Mantle", @@ -12193,7 +13731,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 45, + "req_int": 45 }, { "name": "Runeforged Titan Mitts", @@ -12203,7 +13743,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 64 }, { "name": "Runeforged Tonal Focus", @@ -12214,7 +13755,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 33 }, { "name": "Runeforged Torment Jacket", @@ -12224,7 +13766,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runeforged Torn Gloves", @@ -12245,7 +13789,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 86 }, { "name": "Runeforged Tournament Mail", @@ -12255,7 +13800,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runeforged Trailblazer Armour", @@ -12265,7 +13812,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 52, + "req_dex": 52 }, { "name": "Runeforged Trapper Hood", @@ -12275,7 +13824,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 107 }, { "name": "Runeforged Treerunner Shoes", @@ -12285,7 +13835,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Runeforged Tribal Mask", @@ -12296,7 +13848,9 @@ "helmet", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 27, + "req_int": 27 }, { "name": "Runeforged Trimmed Greaves", @@ -12307,7 +13861,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 38 }, { "name": "Runeforged Twig Circlet", @@ -12339,7 +13894,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 82 }, { "name": "Runeforged Utility Wraps", @@ -12349,7 +13905,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Runeforged Utzaal Cuirass", @@ -12359,7 +13917,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Vaal Crest Shield", @@ -12371,7 +13930,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Runeforged Vaal Cuirass", @@ -12382,7 +13943,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 60 }, { "name": "Runeforged Vaal Gloves", @@ -12392,7 +13954,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 94 }, { "name": "Runeforged Vaal Greaves", @@ -12402,7 +13965,8 @@ "default", "runeforged", "str_armour" - ] + ], + "req_str": 101 }, { "name": "Runeforged Vaal Mitts", @@ -12412,7 +13976,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 94 }, { "name": "Runeforged Vaal Tower Shield", @@ -12424,7 +13989,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 107 }, { "name": "Runeforged Vaal Wraps", @@ -12434,7 +14000,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 52, + "req_int": 52 }, { "name": "Runeforged Vagabond Armour", @@ -12445,7 +14013,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 17, + "req_dex": 17 }, { "name": "Runeforged Veiled Mask", @@ -12456,7 +14026,9 @@ "helmet", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 23, + "req_int": 23 }, { "name": "Runeforged Velour Shoes", @@ -12466,7 +14038,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 38, + "req_int": 38 }, { "name": "Runeforged Velvet Cap", @@ -12476,7 +14050,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 74 }, { "name": "Runeforged Venerated Mantle", @@ -12486,7 +14061,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Runeforged Vermeil Circlet", @@ -12497,7 +14074,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 64 }, { "name": "Runeforged Veteran Sabatons", @@ -12507,7 +14085,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 47, + "req_dex": 47 }, { "name": "Runeforged Vicious Talisman", @@ -12519,7 +14099,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 74, + "req_int": 52 }, { "name": "Runeforged Vile Robe", @@ -12529,7 +14111,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runeforged Viper Cap", @@ -12540,7 +14123,8 @@ "helmet", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 54 }, { "name": "Runeforged Visored Helm", @@ -12551,7 +14135,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 15, + "req_dex": 15 }, { "name": "Runeforged Voodoo Focus", @@ -12562,7 +14148,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 48 }, { "name": "Runeforged Votive Raiment", @@ -12573,7 +14160,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 54 }, { "name": "Runeforged Wanderer Shoes", @@ -12583,7 +14171,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 47, + "req_int": 47 }, { "name": "Runeforged War Spear", @@ -12595,7 +14185,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 23, + "req_dex": 56 }, { "name": "Runeforged War Wraps", @@ -12605,7 +14197,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Runeforged Warded Helm", @@ -12615,7 +14209,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Runeforged Warlock Leggings", @@ -12625,7 +14221,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 56, + "req_int": 56 }, { "name": "Runeforged Warlord Cuirass", @@ -12636,7 +14234,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Runeforged Warmonger Greathelm", @@ -12646,7 +14245,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 91 }, { "name": "Runeforged Warpick", @@ -12658,7 +14258,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 68 }, { "name": "Runeforged Warrior Greathelm", @@ -12669,7 +14270,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 52 }, { "name": "Runeforged Waxed Jacket", @@ -12680,7 +14282,9 @@ "dex_int_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 13, + "req_int": 13 }, { "name": "Runeforged Wayfarer Jacket", @@ -12691,7 +14295,9 @@ "dex_int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 26, + "req_int": 26 }, { "name": "Runeforged Wayward Crest Shield", @@ -12704,7 +14310,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Runeforged Weaver Leggings", @@ -12715,7 +14323,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 34, + "req_int": 34 }, { "name": "Runeforged Whorl Focus", @@ -12726,7 +14336,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 73 }, { "name": "Runeforged Wicker Tiara", @@ -12737,7 +14348,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 17 }, { "name": "Runeforged Winged Spear", @@ -12749,7 +14361,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 23, + "req_dex": 56 }, { "name": "Runeforged Wolfskin Mantle", @@ -12759,7 +14373,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runeforged Wooden Buckler", @@ -12773,7 +14389,8 @@ "ezomyte_basetype", "runeforged", "shield" - ] + ], + "req_dex": 10 }, { "name": "Runeforged Wooden Club", @@ -12785,7 +14402,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 68 }, { "name": "Runeforged Woven Cap", @@ -12795,7 +14413,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 91 }, { "name": "Runeforged Woven Focus", @@ -12806,7 +14425,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 11 }, { "name": "Runeforged Wrapped Cap", @@ -12816,7 +14436,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 77 }, { "name": "Runeforged Wrapped Greathelm", @@ -12827,7 +14448,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 25 }, { "name": "Runeforged Wrapped Sandals", @@ -12838,7 +14460,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 17 }, { "name": "Runeforged Wyrmscale Coat", @@ -12848,7 +14471,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runeforged Zealot Bow", @@ -12861,7 +14485,8 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 97 }, { "name": "Runeforged Zealot Gauntlets", @@ -12871,7 +14496,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Runeforged Zenith Vestments", @@ -12881,7 +14508,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runemastered Aberrant Sledge", @@ -12892,7 +14521,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Runemastered Aged Cuffs", @@ -12903,7 +14533,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 27, + "req_int": 27 }, { "name": "Runemastered Altar Robe", @@ -12914,7 +14546,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 87 }, { "name": "Runemastered Ancestral Mail", @@ -12925,7 +14558,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 57, + "req_dex": 57 }, { "name": "Runemastered Anchorite Garb", @@ -12936,7 +14571,9 @@ "dex_int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 49, + "req_int": 49 }, { "name": "Runemastered Ancient Leggings", @@ -12946,7 +14583,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 56, + "req_int": 56 }, { "name": "Runemastered Ancient Mail", @@ -12956,7 +14595,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runemastered Ancient Visor", @@ -12966,7 +14607,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Runemastered Antler Focus", @@ -12977,7 +14620,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Armoured Cap", @@ -12987,7 +14631,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 91 }, { "name": "Runemastered Armoured Vest", @@ -12997,7 +14642,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runemastered Array Buckler", @@ -13010,7 +14656,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 85 }, { "name": "Runemastered Assassin Garb", @@ -13020,7 +14667,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runemastered Barricade Tower Shield", @@ -13033,7 +14682,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 54 }, { "name": "Runemastered Beaded Circlet", @@ -13044,7 +14694,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Blacksteel Tower Shield", @@ -13056,7 +14707,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 91 }, { "name": "Runemastered Blazon Crest Shield", @@ -13069,7 +14721,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 32, + "req_int": 32 }, { "name": "Runemastered Bolstered Mitts", @@ -13080,7 +14734,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 48 }, { "name": "Runemastered Bone Raiment", @@ -13091,7 +14746,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 61 }, { "name": "Runemastered Braced Sabatons", @@ -13102,7 +14758,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 29, + "req_dex": 29 }, { "name": "Runemastered Braced Tower Shield", @@ -13115,7 +14773,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 54 }, { "name": "Runemastered Brimmed Helm", @@ -13126,7 +14785,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Runemastered Bronze Greaves", @@ -13137,7 +14798,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 51 }, { "name": "Runemastered Burnished Gauntlets", @@ -13147,7 +14809,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Runemastered Chain Mail", @@ -13158,7 +14822,9 @@ "ezomyte_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 35, + "req_dex": 35 }, { "name": "Runemastered Chain Tiara", @@ -13169,7 +14835,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 58 }, { "name": "Runemastered Cinched Boots", @@ -13179,7 +14846,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 86 }, { "name": "Runemastered Cleric Vestments", @@ -13190,7 +14858,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 49, + "req_int": 49 }, { "name": "Runemastered Cloaked Mail", @@ -13201,7 +14871,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 36, + "req_dex": 36 }, { "name": "Runemastered Corsair Cap", @@ -13212,7 +14884,8 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 78 }, { "name": "Runemastered Corvus Mantle", @@ -13222,7 +14895,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runemastered Covered Sabatons", @@ -13233,7 +14908,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Runemastered Covert Hood", @@ -13243,7 +14920,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 79 }, { "name": "Runemastered Cowled Helm", @@ -13254,7 +14932,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 32, + "req_dex": 32 }, { "name": "Runemastered Crescent Targe", @@ -13267,7 +14947,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Runemastered Crucible Tower Shield", @@ -13280,7 +14962,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 91 }, { "name": "Runemastered Crystal Focus", @@ -13291,7 +14974,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 58 }, { "name": "Runemastered Cultist Crown", @@ -13302,7 +14986,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Runemastered Death Mask", @@ -13312,7 +14998,9 @@ "dex_int_armour", "helmet", "runeforged" - ] + ], + "req_dex": 50, + "req_int": 50 }, { "name": "Runemastered Decorated Helm", @@ -13322,7 +15010,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Runemastered Doubled Gauntlets", @@ -13333,7 +15023,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Runemastered Dyad Crossbow", @@ -13347,7 +15039,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Runemastered Effigial Tower Shield", @@ -13360,7 +15054,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 54 }, { "name": "Runemastered Elementalist Robe", @@ -13371,7 +15066,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 87 }, { "name": "Runemastered Elite Greathelm", @@ -13382,7 +15078,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 78 }, { "name": "Runemastered Emblem Crest Shield", @@ -13395,7 +15092,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Runemastered Embossed Boots", @@ -13406,7 +15105,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 51 }, { "name": "Runemastered Embroidered Gloves", @@ -13416,7 +15116,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 64 }, { "name": "Runemastered Engraved Focus", @@ -13427,7 +15128,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Enlightened Robe", @@ -13437,7 +15139,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runemastered Explorer Armour", @@ -13448,7 +15151,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 49, + "req_dex": 49 }, { "name": "Runemastered Exquisite Vest", @@ -13458,7 +15163,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 121 }, { "name": "Runemastered Face Mask", @@ -13469,7 +15175,9 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 31, + "req_int": 31 }, { "name": "Runemastered Familial Talisman", @@ -13481,7 +15189,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_int": 45 }, { "name": "Runemastered Feathered Robe", @@ -13492,7 +15202,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 61 }, { "name": "Runemastered Feathered Sandals", @@ -13503,7 +15214,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 54 }, { "name": "Runemastered Feathered Tiara", @@ -13514,7 +15226,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 78 }, { "name": "Runemastered Felled Greatclub", @@ -13526,7 +15239,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 97 }, { "name": "Runemastered Felt Cap", @@ -13537,7 +15251,8 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Fierce Greathelm", @@ -13548,7 +15263,8 @@ "karui_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 78 }, { "name": "Runemastered Fine Bracers", @@ -13559,7 +15275,8 @@ "gloves", "karui_basetype", "runeforged" - ] + ], + "req_dex": 56 }, { "name": "Runemastered Firm Bracers", @@ -13570,7 +15287,8 @@ "ezomyte_basetype", "gloves", "runeforged" - ] + ], + "req_dex": 48 }, { "name": "Runemastered Full Plate", @@ -13581,7 +15299,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 72 }, { "name": "Runemastered Furtive Wraps", @@ -13591,7 +15310,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 38, + "req_int": 38 }, { "name": "Runemastered Garment", @@ -13612,7 +15333,9 @@ "ezomyte_basetype", "gloves", "runeforged" - ] + ], + "req_dex": 27, + "req_int": 27 }, { "name": "Runemastered Gilded Vestments", @@ -13622,7 +15345,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 57, + "req_int": 57 }, { "name": "Runemastered Glacial Fortress", @@ -13634,7 +15359,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 99 }, { "name": "Runemastered Gold Circlet", @@ -13645,7 +15371,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 78 }, { "name": "Runemastered Goldcast Cuffs", @@ -13656,7 +15383,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Runemastered Grand Cuisses", @@ -13666,7 +15395,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 34, + "req_dex": 34, + "req_int": 34 }, { "name": "Runemastered Grand Manchettes", @@ -13676,7 +15408,10 @@ "gloves", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 32, + "req_dex": 32, + "req_int": 32 }, { "name": "Runemastered Grand Regalia", @@ -13686,7 +15421,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 41, + "req_dex": 41, + "req_int": 41 }, { "name": "Runemastered Grand Visage", @@ -13696,7 +15434,10 @@ "helmet", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 36, + "req_dex": 36, + "req_int": 36 }, { "name": "Runemastered Grasping Mail", @@ -13706,7 +15447,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 41, + "req_dex": 41, + "req_int": 41 }, { "name": "Runemastered Guarded Helm", @@ -13717,7 +15461,9 @@ "helmet", "runeforged", "str_dex_armour" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Runemastered Hardwood Targe", @@ -13730,7 +15476,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Runemastered Havoc Raiment", @@ -13740,7 +15488,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 121 }, { "name": "Runemastered Heavy Belt", @@ -13761,7 +15510,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 43, + "req_int": 43 }, { "name": "Runemastered Heraldric Tower Shield", @@ -13774,7 +15525,8 @@ "str_armour", "str_shield", "vaal_basetype" - ] + ], + "req_str": 71 }, { "name": "Runemastered Hermit Garb", @@ -13785,7 +15537,9 @@ "dex_int_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 35, + "req_int": 35 }, { "name": "Runemastered Heroic Armour", @@ -13795,7 +15549,9 @@ "default", "runeforged", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runemastered Hewn Mask", @@ -13806,7 +15562,9 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 31, + "req_int": 31 }, { "name": "Runemastered Hexer's Robe", @@ -13817,7 +15575,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 61 }, { "name": "Runemastered Hooded Mask", @@ -13828,7 +15587,9 @@ "helmet", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 31, + "req_int": 31 }, { "name": "Runemastered Horned Crown", @@ -13839,7 +15600,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Runemastered Hunter Hood", @@ -13850,7 +15613,8 @@ "helmet", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 58 }, { "name": "Runemastered Hunting Shoes", @@ -13861,7 +15625,9 @@ "dex_int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 30, + "req_int": 30 }, { "name": "Runemastered Intricate Crest Shield", @@ -13873,7 +15639,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Runemastered Intricate Gloves", @@ -13884,7 +15652,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 68 }, { "name": "Runemastered Iron Buckler", @@ -13898,7 +15667,8 @@ "maraketh_basetype", "runeforged", "shield" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Iron Crown", @@ -13909,7 +15679,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Runemastered Iron Cuirass", @@ -13920,7 +15692,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 61 }, { "name": "Runemastered Iron Greaves", @@ -13931,7 +15704,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 51 }, { "name": "Runemastered Ironclad Vestments", @@ -13942,7 +15716,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 49, + "req_int": 49 }, { "name": "Runemastered Ironhead Spear", @@ -13954,7 +15730,9 @@ "runeforged", "spear", "weapon" - ] + ], + "req_str": 31, + "req_dex": 76 }, { "name": "Runemastered Jade Tiara", @@ -13965,7 +15743,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runemastered Jewelled Gloves", @@ -13976,7 +15755,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 48 }, { "name": "Runemastered Jingling Crest Shield", @@ -13989,7 +15769,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 31, + "req_int": 31 }, { "name": "Runemastered Keth Raiment", @@ -14000,7 +15782,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 61 }, { "name": "Runemastered Knight Armour", @@ -14011,7 +15794,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 49, + "req_dex": 49 }, { "name": "Runemastered Laced Boots", @@ -14022,7 +15807,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 51 }, { "name": "Runemastered Lattice Sandals", @@ -14033,7 +15819,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 51 }, { "name": "Runemastered Layered Gauntlets", @@ -14044,7 +15831,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 27, + "req_dex": 27 }, { "name": "Runemastered Leather Buckler", @@ -14058,7 +15847,8 @@ "ezomyte_basetype", "runeforged", "shield" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Leather Vest", @@ -14069,7 +15859,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 61 }, { "name": "Runemastered Leatherbound Hood", @@ -14080,7 +15871,8 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 91 }, { "name": "Runemastered Linen Wraps", @@ -14091,7 +15883,9 @@ "gloves", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 27, + "req_int": 27 }, { "name": "Runemastered Lizardscale Boots", @@ -14102,7 +15896,8 @@ "dex_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Magus Tiara", @@ -14112,7 +15907,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runemastered Mail Sabatons", @@ -14123,7 +15919,9 @@ "ezomyte_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 29, + "req_dex": 29 }, { "name": "Runemastered Mail Vestments", @@ -14134,7 +15932,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 35, + "req_int": 35 }, { "name": "Runemastered Marabout Garb", @@ -14145,7 +15945,9 @@ "dex_int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 35, + "req_int": 35 }, { "name": "Runemastered Maraketh Cuirass", @@ -14156,7 +15958,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 61 }, { "name": "Runemastered Martyr Crown", @@ -14167,7 +15970,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 32, + "req_int": 32 }, { "name": "Runemastered Moulded Mitts", @@ -14178,7 +15983,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 68 }, { "name": "Runemastered Nettle Talisman", @@ -14190,7 +15996,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_int": 45 }, { "name": "Runemastered Oak Greathammer", @@ -14202,7 +16010,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 97 }, { "name": "Runemastered Omen Crest Shield", @@ -14215,7 +16024,9 @@ "str_int_armour", "str_int_shield", "vaal_basetype" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Runemastered Ornate Buckler", @@ -14228,7 +16039,8 @@ "dex_shield", "runeforged", "shield" - ] + ], + "req_dex": 78 }, { "name": "Runemastered Ornate Ringmail", @@ -14238,7 +16050,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 57, + "req_int": 57 }, { "name": "Runemastered Painted Tower Shield", @@ -14251,7 +16065,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 54 }, { "name": "Runemastered Pathfinder Coat", @@ -14262,7 +16077,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 61 }, { "name": "Runemastered Pauascale Gloves", @@ -14273,7 +16089,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 68 }, { "name": "Runemastered Pilgrim Vestments", @@ -14284,7 +16101,9 @@ "ezomyte_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 35, + "req_int": 35 }, { "name": "Runemastered Plate Gauntlets", @@ -14295,7 +16114,9 @@ "karui_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 38, + "req_dex": 38 }, { "name": "Runemastered Plated Buckler", @@ -14309,7 +16130,8 @@ "ezomyte_basetype", "runeforged", "shield" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Plated Raiment", @@ -14319,7 +16141,8 @@ "default", "int_armour", "runeforged" - ] + ], + "req_int": 103 }, { "name": "Runemastered Plumed Focus", @@ -14330,7 +16153,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 91 }, { "name": "Runemastered Primal Markings", @@ -14340,7 +16164,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Runemastered Quilted Vest", @@ -14351,7 +16177,8 @@ "dex_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 61 }, { "name": "Runemastered Raider Plate", @@ -14362,7 +16189,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 61 }, { "name": "Runemastered Rampart Tower Shield", @@ -14375,7 +16203,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 58 }, { "name": "Runemastered Revered Vestments", @@ -14385,7 +16214,9 @@ "default", "runeforged", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Runemastered Rhoahide Coat", @@ -14396,7 +16227,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 61 }, { "name": "Runemastered Ridged Buckler", @@ -14410,7 +16242,8 @@ "maraketh_basetype", "runeforged", "shield" - ] + ], + "req_dex": 58 }, { "name": "Runemastered Ringmail Gauntlets", @@ -14421,7 +16254,9 @@ "gloves", "runeforged", "str_dex_armour" - ] + ], + "req_str": 27, + "req_dex": 27 }, { "name": "Runemastered Riveted Mitts", @@ -14432,7 +16267,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 48 }, { "name": "Runemastered Rogue Armour", @@ -14443,7 +16279,9 @@ "ezomyte_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 35, + "req_dex": 35 }, { "name": "Runemastered Rope Cuffs", @@ -14454,7 +16292,9 @@ "gloves", "runeforged", "str_int_armour" - ] + ], + "req_str": 27, + "req_int": 27 }, { "name": "Runemastered Rough Greaves", @@ -14465,7 +16305,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 51 }, { "name": "Runemastered Runic Fork", @@ -14474,7 +16315,8 @@ "onehand", "runeforged", "wand" - ] + ], + "req_int": 114 }, { "name": "Runemastered Rusted Cuirass", @@ -14485,7 +16327,8 @@ "ezomyte_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 61 }, { "name": "Runemastered Rusted Greathelm", @@ -14496,7 +16339,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 54 }, { "name": "Runemastered Sacrificial Mantle", @@ -14507,7 +16351,9 @@ "runeforged", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 49, + "req_int": 49 }, { "name": "Runemastered Sacrificial Regalia", @@ -14517,7 +16363,10 @@ "default", "runeforged", "str_dex_int_armour" - ] + ], + "req_str": 72, + "req_dex": 72, + "req_int": 72 }, { "name": "Runemastered Scale Mail", @@ -14528,7 +16377,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 49, + "req_dex": 49 }, { "name": "Runemastered Scalper's Jacket", @@ -14539,7 +16390,9 @@ "dex_int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 49, + "req_int": 49 }, { "name": "Runemastered Scout's Vest", @@ -14550,7 +16403,8 @@ "dex_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 87 }, { "name": "Runemastered Sectioned Bracers", @@ -14561,7 +16415,8 @@ "gloves", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 48 }, { "name": "Runemastered Secured Leggings", @@ -14572,7 +16427,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 29, + "req_int": 29 }, { "name": "Runemastered Serpentscale Coat", @@ -14583,7 +16440,8 @@ "dex_armour", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 87 }, { "name": "Runemastered Shabby Hood", @@ -14594,7 +16452,8 @@ "ezomyte_basetype", "helmet", "runeforged" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Shaman Mantle", @@ -14605,7 +16464,9 @@ "maraketh_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 35, + "req_int": 35 }, { "name": "Runemastered Shielded Helm", @@ -14616,7 +16477,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 43, + "req_dex": 43 }, { "name": "Runemastered Shrouded Vest", @@ -14627,7 +16490,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 61 }, { "name": "Runemastered Sigil Crest Shield", @@ -14640,7 +16504,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 32, + "req_int": 32 }, { "name": "Runemastered Silk Robe", @@ -14651,7 +16517,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 61 }, { "name": "Runemastered Silk Slippers", @@ -14662,7 +16529,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 51 }, { "name": "Runemastered Smuggler Coat", @@ -14673,7 +16541,8 @@ "dex_armour", "karui_basetype", "runeforged" - ] + ], + "req_dex": 103 }, { "name": "Runemastered Soldier Greathelm", @@ -14684,7 +16553,8 @@ "helmet", "runeforged", "str_armour" - ] + ], + "req_str": 54 }, { "name": "Runemastered Solid Mask", @@ -14695,7 +16565,9 @@ "helmet", "karui_basetype", "runeforged" - ] + ], + "req_dex": 43, + "req_int": 43 }, { "name": "Runemastered Sombre Gloves", @@ -14706,7 +16578,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 49 }, { "name": "Runemastered Spiked Buckler", @@ -14720,7 +16593,8 @@ "maraketh_basetype", "runeforged", "shield" - ] + ], + "req_dex": 58 }, { "name": "Runemastered Spined Bracers", @@ -14731,7 +16605,8 @@ "gloves", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 48 }, { "name": "Runemastered Spiral Wraps", @@ -14741,7 +16616,9 @@ "dex_int_armour", "gloves", "vaal_basetype" - ] + ], + "req_dex": 38, + "req_int": 38 }, { "name": "Runemastered Spired Greathelm", @@ -14752,7 +16629,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 58 }, { "name": "Runemastered Spiritbone Crown", @@ -14763,7 +16641,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 50, + "req_int": 50 }, { "name": "Runemastered Splintered Tower Shield", @@ -14776,7 +16656,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 54 }, { "name": "Runemastered Stacked Sabatons", @@ -14787,7 +16668,9 @@ "runeforged", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 30, + "req_dex": 30 }, { "name": "Runemastered Steel Plate", @@ -14798,7 +16681,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 65 }, { "name": "Runemastered Steeltoe Boots", @@ -14809,7 +16693,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Stitched Gloves", @@ -14820,7 +16705,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 48 }, { "name": "Runemastered Stocky Mitts", @@ -14831,7 +16717,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 48 }, { "name": "Runemastered Stone Greaves", @@ -14842,7 +16729,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 73 }, { "name": "Runemastered Straw Sandals", @@ -14853,7 +16741,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 51 }, { "name": "Runemastered Strider Vest", @@ -14863,7 +16752,8 @@ "default", "dex_armour", "runeforged" - ] + ], + "req_dex": 103 }, { "name": "Runemastered Studded Greatclub", @@ -14875,7 +16765,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 97 }, { "name": "Runemastered Studded Vest", @@ -14886,7 +16777,8 @@ "dex_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 65 }, { "name": "Runemastered Suede Bracers", @@ -14897,7 +16789,8 @@ "ezomyte_basetype", "gloves", "runeforged" - ] + ], + "req_dex": 48 }, { "name": "Runemastered Tattered Robe", @@ -14908,7 +16801,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 61 }, { "name": "Runemastered Tempered Mitts", @@ -14919,7 +16813,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 48 }, { "name": "Runemastered Tenebrous Crown", @@ -14929,7 +16824,9 @@ "helmet", "runeforged", "str_int_armour" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Runemastered Tense Crossbow", @@ -14943,7 +16840,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 54, + "req_dex": 54 }, { "name": "Runemastered Threaded Shoes", @@ -14954,7 +16853,9 @@ "dex_int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 29, + "req_int": 29 }, { "name": "Runemastered Tideseer Mantle", @@ -14965,7 +16866,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 57, + "req_int": 57 }, { "name": "Runemastered Titan Mitts", @@ -14975,7 +16878,8 @@ "gloves", "runeforged", "str_armour" - ] + ], + "req_str": 80 }, { "name": "Runemastered Tonal Focus", @@ -14986,7 +16890,8 @@ "int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Torment Club", @@ -14997,7 +16902,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 124 }, { "name": "Runemastered Torn Gloves", @@ -15008,7 +16914,8 @@ "gloves", "int_armour", "runeforged" - ] + ], + "req_int": 48 }, { "name": "Runemastered Tribal Mask", @@ -15019,7 +16926,9 @@ "helmet", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Runemastered Trimmed Greaves", @@ -15030,7 +16939,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 54 }, { "name": "Runemastered Twig Circlet", @@ -15041,7 +16951,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Twig Focus", @@ -15052,7 +16963,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Twilight Tiara", @@ -15062,7 +16974,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 91 }, { "name": "Runemastered Utility Wraps", @@ -15072,7 +16985,9 @@ "dex_int_armour", "gloves", "runeforged" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Runemastered Vaal Cuirass", @@ -15083,7 +16998,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 87 }, { "name": "Runemastered Vaal Tower Shield", @@ -15095,7 +17011,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 107 }, { "name": "Runemastered Vagabond Armour", @@ -15106,7 +17023,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 35, + "req_dex": 35 }, { "name": "Runemastered Veiled Mask", @@ -15117,7 +17036,9 @@ "helmet", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 32, + "req_int": 32 }, { "name": "Runemastered Velour Shoes", @@ -15127,7 +17048,9 @@ "default", "dex_int_armour", "runeforged" - ] + ], + "req_dex": 47, + "req_int": 47 }, { "name": "Runemastered Velvet Cap", @@ -15137,7 +17060,8 @@ "dex_armour", "helmet", "runeforged" - ] + ], + "req_dex": 91 }, { "name": "Runemastered Venerable Defender", @@ -15150,7 +17074,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Runemastered Veridical Chain", @@ -15169,7 +17095,9 @@ "karui_basetype", "runeforged", "str_int_armour" - ] + ], + "req_str": 38, + "req_int": 38 }, { "name": "Runemastered Vermeil Circlet", @@ -15180,7 +17108,8 @@ "int_armour", "karui_basetype", "runeforged" - ] + ], + "req_int": 78 }, { "name": "Runemastered Viper Cap", @@ -15191,7 +17120,8 @@ "helmet", "runeforged", "vaal_basetype" - ] + ], + "req_dex": 78 }, { "name": "Runemastered Visored Helm", @@ -15202,7 +17132,9 @@ "maraketh_basetype", "runeforged", "str_dex_armour" - ] + ], + "req_str": 31, + "req_dex": 31 }, { "name": "Runemastered Voodoo Focus", @@ -15213,7 +17145,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 78 }, { "name": "Runemastered Votive Raiment", @@ -15224,7 +17157,8 @@ "int_armour", "runeforged", "vaal_basetype" - ] + ], + "req_int": 87 }, { "name": "Runemastered Warding Quarterstaff", @@ -15235,7 +17169,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Runemastered Warrior Greathelm", @@ -15246,7 +17182,8 @@ "runeforged", "str_armour", "vaal_basetype" - ] + ], + "req_str": 78 }, { "name": "Runemastered Waxed Jacket", @@ -15257,7 +17194,9 @@ "dex_int_armour", "ezomyte_basetype", "runeforged" - ] + ], + "req_dex": 35, + "req_int": 35 }, { "name": "Runemastered Wayfarer Jacket", @@ -15268,7 +17207,9 @@ "dex_int_armour", "maraketh_basetype", "runeforged" - ] + ], + "req_dex": 36, + "req_int": 36 }, { "name": "Runemastered Wicker Tiara", @@ -15279,7 +17220,8 @@ "helmet", "int_armour", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Wooden Buckler", @@ -15293,7 +17235,8 @@ "ezomyte_basetype", "runeforged", "shield" - ] + ], + "req_dex": 54 }, { "name": "Runemastered Wooden Club", @@ -15305,7 +17248,8 @@ "onehand", "runeforged", "weapon" - ] + ], + "req_str": 97 }, { "name": "Runemastered Woven Focus", @@ -15316,7 +17260,8 @@ "focus", "int_armour", "runeforged" - ] + ], + "req_int": 54 }, { "name": "Runemastered Wrapped Greathelm", @@ -15327,7 +17272,8 @@ "maraketh_basetype", "runeforged", "str_armour" - ] + ], + "req_str": 54 }, { "name": "Runemastered Wrapped Sandals", @@ -15338,7 +17284,8 @@ "ezomyte_basetype", "int_armour", "runeforged" - ] + ], + "req_int": 51 }, { "name": "Runic Fork", @@ -15346,7 +17293,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 114 }, { "name": "Runic Shortsword", @@ -15356,7 +17304,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 49, + "req_dex": 49 }, { "name": "Runner Vest", @@ -15365,7 +17315,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 94 }, { "name": "Rusted Cuirass", @@ -15401,7 +17352,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 121 }, { "name": "Sacred Focus", @@ -15410,7 +17362,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 107 }, { "name": "Sacred Maul", @@ -15420,7 +17373,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 149 }, { "name": "Sacrificial Axe", @@ -15431,7 +17385,9 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_str": 52, + "req_dex": 22 }, { "name": "Sacrificial Mantle", @@ -15441,7 +17397,9 @@ "default", "str_int_armour", "vaal_basetype" - ] + ], + "req_str": 33, + "req_int": 33 }, { "name": "Sacrificial Regalia", @@ -15450,7 +17408,10 @@ "body_armour", "default", "str_dex_int_armour" - ] + ], + "req_str": 72, + "req_dex": 72, + "req_int": 72 }, { "name": "Saintly Crown", @@ -15459,7 +17420,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 54, + "req_int": 54 }, { "name": "Sanctified Staff", @@ -15467,7 +17430,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 99 }, { "name": "Sandsworn Cuirass", @@ -15476,7 +17440,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 82 }, { "name": "Sandsworn Sandals", @@ -15485,7 +17450,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 101 }, { "name": "Sandsworn Tiara", @@ -15494,7 +17460,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 77 }, { "name": "Sapphire", @@ -15533,7 +17500,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 39, + "req_dex": 17 }, { "name": "Scale Mail", @@ -15543,7 +17512,9 @@ "default", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Scalper's Jacket", @@ -15553,7 +17524,9 @@ "default", "dex_int_armour", "vaal_basetype" - ] + ], + "req_dex": 35, + "req_int": 35 }, { "name": "Scimitar", @@ -15564,7 +17537,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 19, + "req_dex": 19 }, { "name": "Scoundrel Armour", @@ -15573,7 +17548,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 43, + "req_dex": 43 }, { "name": "Scoundrel Jacket", @@ -15583,7 +17560,9 @@ "default", "dex_int_armour", "karui_basetype" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Scout's Vest", @@ -15593,7 +17572,8 @@ "default", "dex_armour", "vaal_basetype" - ] + ], + "req_dex": 54 }, { "name": "Seaglass Spear", @@ -15604,7 +17584,10 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 29, + "req_dex": 71, + "req_int": 50 }, { "name": "Seastorm Mantle", @@ -15614,7 +17597,9 @@ "default", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Sectarian Crest Shield", @@ -15626,7 +17611,9 @@ "str_int_armour", "str_int_shield", "vaal_basetype" - ] + ], + "req_str": 27, + "req_int": 27 }, { "name": "Sectioned Bracers", @@ -15636,7 +17623,8 @@ "dex_armour", "gloves", "maraketh_basetype" - ] + ], + "req_dex": 36 }, { "name": "Secured Leggings", @@ -15646,7 +17634,9 @@ "default", "maraketh_basetype", "str_int_armour" - ] + ], + "req_str": 14, + "req_int": 14 }, { "name": "Secured Wraps", @@ -15656,7 +17646,9 @@ "dex_int_armour", "gloves", "karui_basetype" - ] + ], + "req_dex": 55, + "req_int": 55 }, { "name": "Seer Crest Shield", @@ -15668,7 +17660,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 39, + "req_int": 39 }, { "name": "Sekhema Sandals", @@ -15678,7 +17672,8 @@ "default", "int_armour", "karui_basetype" - ] + ], + "req_int": 108 }, { "name": "Sekheman Crest Shield", @@ -15689,7 +17684,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 54, + "req_int": 54 }, { "name": "Sentinel Greathelm", @@ -15698,7 +17695,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 74 }, { "name": "Serpentscale Boots", @@ -15707,7 +17705,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 78 }, { "name": "Serpentscale Coat", @@ -15717,7 +17716,8 @@ "default", "dex_armour", "vaal_basetype" - ] + ], + "req_dex": 59 }, { "name": "Serrated Quiver", @@ -15743,7 +17743,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 41, + "req_int": 41 }, { "name": "Shaman Mantle", @@ -15753,7 +17755,9 @@ "default", "maraketh_basetype", "str_int_armour" - ] + ], + "req_str": 26, + "req_int": 26 }, { "name": "Shamanistic Leggings", @@ -15762,7 +17766,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 44, + "req_int": 44 }, { "name": "Sharktooth Claw", @@ -15772,7 +17778,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 87 }, { "name": "Shielded Helm", @@ -15782,7 +17789,9 @@ "helmet", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 27, + "req_dex": 27 }, { "name": "Shortbow", @@ -15794,7 +17803,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 12 }, { "name": "Shortsword", @@ -15822,7 +17832,9 @@ "default", "onehand", "sceptre" - ] + ], + "req_str": 17, + "req_int": 38 }, { "name": "Shrine Sceptre (Purity of Cold)", @@ -15831,7 +17843,9 @@ "default", "onehand", "sceptre" - ] + ], + "req_str": 17, + "req_int": 38 }, { "name": "Shrine Sceptre (Purity of Fire)", @@ -15840,7 +17854,9 @@ "default", "onehand", "sceptre" - ] + ], + "req_str": 17, + "req_int": 38 }, { "name": "Shrine Sceptre (Purity of Lighting)", @@ -15849,7 +17865,9 @@ "default", "onehand", "sceptre" - ] + ], + "req_str": 17, + "req_int": 38 }, { "name": "Shrouded Mail", @@ -15858,7 +17876,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Shrouded Vest", @@ -15868,7 +17888,8 @@ "default", "dex_armour", "maraketh_basetype" - ] + ], + "req_dex": 28 }, { "name": "Sickle Sword", @@ -15879,7 +17900,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 29, + "req_dex": 29 }, { "name": "Siege Crossbow", @@ -15891,7 +17914,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 89 }, { "name": "Sigil Crest Shield", @@ -15903,7 +17928,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 8, + "req_int": 8 }, { "name": "Signet Cuffs", @@ -15912,7 +17939,9 @@ "default", "gloves", "str_int_armour" - ] + ], + "req_str": 44, + "req_int": 44 }, { "name": "Silk Robe", @@ -15922,7 +17951,8 @@ "default", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 37 }, { "name": "Silk Slippers", @@ -15932,7 +17962,8 @@ "default", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 38 }, { "name": "Silver Charm", @@ -15952,7 +17983,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 38, + "req_int": 38 }, { "name": "Simple Dagger", @@ -15963,7 +17996,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 15, + "req_int": 15 }, { "name": "Sinew Belt", @@ -15982,7 +18017,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 104, + "req_int": 41 }, { "name": "Siphoning Wand", @@ -15990,7 +18027,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 23 }, { "name": "Sirenscale Gloves", @@ -16000,7 +18038,8 @@ "gloves", "int_armour", "karui_basetype" - ] + ], + "req_int": 101 }, { "name": "Skinning Knife", @@ -16011,7 +18050,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 19, + "req_int": 19 }, { "name": "Skulking Mask", @@ -16020,7 +18061,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 40, + "req_int": 40 }, { "name": "Skullcrusher Quarterstaff", @@ -16031,7 +18074,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Skycrown Tiara", @@ -16040,7 +18085,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 91 }, { "name": "Slayer Armour", @@ -16049,7 +18095,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Sleek Boots", @@ -16058,7 +18106,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 68 }, { "name": "Sleek Jacket", @@ -16067,7 +18116,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Slender Flail", @@ -16078,7 +18129,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 38, + "req_int": 17 }, { "name": "Slicing Quarterstaff", @@ -16089,7 +18142,9 @@ "vaal_basetype", "warstaff", "weapon" - ] + ], + "req_dex": 47, + "req_int": 20 }, { "name": "Slim Mace", @@ -16100,7 +18155,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 21 }, { "name": "Slipstrike Vest", @@ -16109,7 +18165,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 121 }, { "name": "Smithing Hammer", @@ -16120,7 +18177,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 11 }, { "name": "Smooth Quarterstaff", @@ -16131,7 +18189,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 68, + "req_int": 28 }, { "name": "Smuggler Coat", @@ -16141,7 +18201,8 @@ "default", "dex_armour", "karui_basetype" - ] + ], + "req_dex": 82 }, { "name": "Snakewood Greathammer", @@ -16151,7 +18212,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 80 }, { "name": "Snakewood Shortbow", @@ -16162,7 +18224,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 80 }, { "name": "Soaring Mask", @@ -16171,7 +18234,9 @@ "default", "dex_int_armour", "helmet" - ] + ], + "req_dex": 59, + "req_int": 59 }, { "name": "Soaring Spear", @@ -16182,7 +18247,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 41, + "req_dex": 115 }, { "name": "Soaring Targe", @@ -16193,7 +18260,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 59, + "req_dex": 59 }, { "name": "Solar Amulet", @@ -16209,7 +18278,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Soldier Greathelm", @@ -16219,7 +18289,8 @@ "ezomyte_basetype", "helmet", "str_armour" - ] + ], + "req_str": 19 }, { "name": "Soldier Helm", @@ -16228,7 +18299,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 40, + "req_dex": 40 }, { "name": "Soldiering Sabatons", @@ -16237,7 +18310,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Solemn Maul", @@ -16247,7 +18322,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 96 }, { "name": "Solid Mask", @@ -16257,7 +18333,9 @@ "dex_int_armour", "helmet", "karui_basetype" - ] + ], + "req_dex": 36, + "req_int": 36 }, { "name": "Sombre Gloves", @@ -16267,7 +18345,8 @@ "ezomyte_basetype", "gloves", "int_armour" - ] + ], + "req_int": 17 }, { "name": "Sorcerous Tiara", @@ -16276,7 +18355,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 99 }, { "name": "Spike Trap", @@ -16293,7 +18373,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 73 }, { "name": "Spiked Buckler", @@ -16306,7 +18387,8 @@ "dex_shield", "maraketh_basetype", "shield" - ] + ], + "req_dex": 38 }, { "name": "Spiked Club", @@ -16317,7 +18399,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 31 }, { "name": "Spiked Spear", @@ -16327,7 +18410,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 50, + "req_dex": 127 }, { "name": "Spikeward Buckler", @@ -16339,7 +18424,8 @@ "dex_armour", "dex_shield", "shield" - ] + ], + "req_dex": 77 }, { "name": "Spined Bracers", @@ -16349,7 +18435,8 @@ "dex_armour", "gloves", "vaal_basetype" - ] + ], + "req_dex": 42 }, { "name": "Spiny Talisman", @@ -16360,7 +18447,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 86, + "req_int": 65 }, { "name": "Spiral Wraps", @@ -16370,7 +18459,9 @@ "dex_int_armour", "gloves", "vaal_basetype" - ] + ], + "req_dex": 24, + "req_int": 24 }, { "name": "Spired Greathelm", @@ -16380,7 +18471,8 @@ "helmet", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 40 }, { "name": "Spiritbone Crown", @@ -16390,7 +18482,9 @@ "helmet", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Splintered Flail", @@ -16432,7 +18526,8 @@ "default", "staff", "twohand" - ] + ], + "req_int": 23 }, { "name": "Sprinters Leg", @@ -16448,7 +18543,9 @@ "default", "str_dex_armour", "vaal_basetype" - ] + ], + "req_str": 26, + "req_dex": 26 }, { "name": "Staghorn Focus", @@ -16457,7 +18554,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 69 }, { "name": "Stalking Belt", @@ -16475,7 +18573,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 80 }, { "name": "Stalking Spear", @@ -16485,7 +18584,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 50, + "req_dex": 127 }, { "name": "Staunching Charm", @@ -16506,7 +18607,8 @@ "default", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 45 }, { "name": "Steelhead Spear", @@ -16516,7 +18618,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 26, + "req_dex": 63 }, { "name": "Steelmail Gauntlets", @@ -16525,7 +18629,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 44, + "req_dex": 44 }, { "name": "Steelpoint Quarterstaff", @@ -16536,7 +18642,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 41, + "req_int": 18 }, { "name": "Steelpoint Shoes", @@ -16546,7 +18654,9 @@ "default", "dex_int_armour", "karui_basetype" - ] + ], + "req_dex": 34, + "req_int": 34 }, { "name": "Steeltoe Boots", @@ -16556,7 +18666,8 @@ "default", "dex_armour", "maraketh_basetype" - ] + ], + "req_dex": 39 }, { "name": "Stellar Amulet", @@ -16573,7 +18684,8 @@ "gloves", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 22 }, { "name": "Stocky Mitts", @@ -16594,7 +18706,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Stoic Sceptre", @@ -16603,7 +18717,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 12 }, { "name": "Stone Charm", @@ -16623,7 +18738,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 98 }, { "name": "Stone Flail", @@ -16634,7 +18750,9 @@ "onehand", "vaal_basetype", "weapon" - ] + ], + "req_str": 47, + "req_int": 20 }, { "name": "Stone Greatsword", @@ -16645,7 +18763,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 34, + "req_dex": 34 }, { "name": "Stone Greaves", @@ -16655,7 +18775,8 @@ "default", "str_armour", "vaal_basetype" - ] + ], + "req_str": 45 }, { "name": "Stone Targe", @@ -16666,7 +18787,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 46, + "req_dex": 46 }, { "name": "Stone Tower Shield", @@ -16678,7 +18801,8 @@ "str_armour", "str_shield", "vaal_basetype" - ] + ], + "req_str": 52 }, { "name": "Stout Crossbow", @@ -16690,7 +18814,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 74, + "req_dex": 74 }, { "name": "Stratified Targe", @@ -16701,7 +18827,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 37, + "req_dex": 37 }, { "name": "Straw Sandals", @@ -16720,7 +18848,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 83 }, { "name": "Strife Pick", @@ -16730,7 +18859,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Striking Quarterstaff", @@ -16740,7 +18870,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Striking Spear", @@ -16750,7 +18882,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 31, + "req_dex": 76 }, { "name": "Structured Hammer", @@ -16760,7 +18894,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 109 }, { "name": "Studded Boots", @@ -16769,7 +18904,8 @@ "boots", "default", "dex_armour" - ] + ], + "req_dex": 72 }, { "name": "Studded Greatclub", @@ -16780,7 +18916,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 31 }, { "name": "Studded Sandals", @@ -16789,7 +18926,8 @@ "boots", "default", "int_armour" - ] + ], + "req_int": 69 }, { "name": "Studded Targe", @@ -16801,7 +18939,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 15, + "req_dex": 15 }, { "name": "Studded Vest", @@ -16811,7 +18951,8 @@ "default", "dex_armour", "maraketh_basetype" - ] + ], + "req_dex": 43 }, { "name": "Sturdy Crossbow", @@ -16824,7 +18965,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 13, + "req_dex": 13 }, { "name": "Sturdy Leg", @@ -16856,7 +18999,8 @@ "dex_armour", "helmet", "maraketh_basetype" - ] + ], + "req_dex": 38 }, { "name": "Swift Bracers", @@ -16865,7 +19009,8 @@ "default", "dex_armour", "gloves" - ] + ], + "req_dex": 64 }, { "name": "Swift Spear", @@ -16875,7 +19020,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 29, + "req_dex": 71 }, { "name": "Swiftstalker Coat", @@ -16884,7 +19031,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 121 }, { "name": "Sword Spear", @@ -16894,7 +19042,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 29, + "req_dex": 72 }, { "name": "Talon Claw", @@ -16904,7 +19054,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 114 }, { "name": "Tasalian Focus", @@ -16914,7 +19065,8 @@ "focus", "int_armour", "karui_basetype" - ] + ], + "req_int": 115 }, { "name": "Tasalian Greaves", @@ -16924,7 +19076,8 @@ "default", "karui_basetype", "str_armour" - ] + ], + "req_str": 108 }, { "name": "Tattered Robe", @@ -16946,7 +19099,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Tawhoan Greatclub", @@ -16957,7 +19112,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 163 }, { "name": "Tawhoan Tower Shield", @@ -16969,7 +19125,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 115 }, { "name": "Tearing Flail", @@ -16979,7 +19136,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 72, + "req_int": 29 }, { "name": "Tempered Mitts", @@ -16989,7 +19148,8 @@ "gloves", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 22 }, { "name": "Templar Vestments", @@ -16998,7 +19158,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 41, + "req_int": 41 }, { "name": "Temple Maul", @@ -17009,7 +19171,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 52 }, { "name": "Tenebrous Amulet", @@ -17025,7 +19188,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Tenebrous Ring", @@ -17047,7 +19212,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 8, + "req_dex": 8 }, { "name": "Tethering Bands", @@ -17057,7 +19224,9 @@ "gloves", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 55, + "req_int": 55 }, { "name": "Thane Mail", @@ -17067,7 +19236,9 @@ "default", "karui_basetype", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Thawing Charm", @@ -17088,7 +19259,9 @@ "default", "dex_int_armour", "maraketh_basetype" - ] + ], + "req_dex": 14, + "req_int": 14 }, { "name": "Thunder Talisman", @@ -17099,7 +19272,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 102, + "req_int": 72 }, { "name": "Tideseer Mantle", @@ -17109,7 +19284,9 @@ "default", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 45, + "req_int": 45 }, { "name": "Time-Lost Diamond", @@ -17171,7 +19348,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 64 }, { "name": "Tonal Focus", @@ -17181,7 +19359,8 @@ "focus", "int_armour", "maraketh_basetype" - ] + ], + "req_int": 33 }, { "name": "Topaz Charm", @@ -17211,7 +19390,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 114 }, { "name": "Torment Jacket", @@ -17220,7 +19400,9 @@ "body_armour", "default", "dex_int_armour" - ] + ], + "req_dex": 67, + "req_int": 67 }, { "name": "Torn Gloves", @@ -17238,7 +19420,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 87 }, { "name": "Totemic Greatclub", @@ -17249,7 +19432,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89 }, { "name": "Totemic Greaves", @@ -17258,7 +19442,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 86 }, { "name": "Tournament Mail", @@ -17267,7 +19452,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 67, + "req_dex": 67 }, { "name": "Toxic Quiver", @@ -17283,7 +19470,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 52, + "req_dex": 52 }, { "name": "Transcendent Life Flask", @@ -17308,7 +19497,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 107 }, { "name": "Trarthan Cannon", @@ -17320,7 +19510,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 114, + "req_dex": 63 }, { "name": "Treasured Blade", @@ -17331,7 +19523,9 @@ "sword", "vaal_basetype", "weapon" - ] + ], + "req_str": 40, + "req_dex": 40 }, { "name": "Treerunner Shoes", @@ -17340,7 +19534,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Tribal Bow", @@ -17352,7 +19548,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 89 }, { "name": "Tribal Mask", @@ -17362,7 +19559,9 @@ "dex_int_armour", "helmet", "vaal_basetype" - ] + ], + "req_dex": 27, + "req_int": 27 }, { "name": "Trimmed Greaves", @@ -17372,7 +19571,8 @@ "default", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 38 }, { "name": "Twig Circlet", @@ -17401,7 +19601,8 @@ "default", "helmet", "int_armour" - ] + ], + "req_int": 82 }, { "name": "Twin Bow", @@ -17412,7 +19613,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 96 }, { "name": "Twin Crossbow", @@ -17424,7 +19626,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 53, + "req_dex": 53 }, { "name": "Twin Flail", @@ -17435,7 +19639,9 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 30, + "req_int": 14 }, { "name": "Twisted Amulet", @@ -17450,7 +19656,8 @@ "default", "onehand", "wand" - ] + ], + "req_int": 114 }, { "name": "Two-Point Quiver", @@ -17492,7 +19699,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 63, + "req_dex": 63 }, { "name": "Unset Ring", @@ -17527,7 +19736,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Utzaal Cuirass", @@ -17536,7 +19747,8 @@ "body_armour", "default", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Vaal Crest Shield", @@ -17547,7 +19759,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 59, + "req_int": 59 }, { "name": "Vaal Cuirass", @@ -17557,7 +19771,8 @@ "default", "str_armour", "vaal_basetype" - ] + ], + "req_str": 60 }, { "name": "Vaal Gloves", @@ -17566,7 +19781,8 @@ "default", "gloves", "int_armour" - ] + ], + "req_int": 94 }, { "name": "Vaal Greaves", @@ -17575,7 +19791,8 @@ "boots", "default", "str_armour" - ] + ], + "req_str": 101 }, { "name": "Vaal Mitts", @@ -17584,7 +19801,8 @@ "default", "gloves", "str_armour" - ] + ], + "req_str": 94 }, { "name": "Vaal Tower Shield", @@ -17595,7 +19813,8 @@ "shield", "str_armour", "str_shield" - ] + ], + "req_str": 107 }, { "name": "Vaal Wraps", @@ -17604,7 +19823,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 52, + "req_int": 52 }, { "name": "Vagabond Armour", @@ -17614,7 +19835,9 @@ "default", "maraketh_basetype", "str_dex_armour" - ] + ], + "req_str": 17, + "req_dex": 17 }, { "name": "Vampiric Blade", @@ -17625,7 +19848,9 @@ "onehand", "sword", "weapon" - ] + ], + "req_str": 14, + "req_dex": 14 }, { "name": "Varnished Crossbow", @@ -17638,7 +19863,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 19, + "req_dex": 19 }, { "name": "Veiled Mask", @@ -17648,7 +19875,9 @@ "dex_int_armour", "helmet", "maraketh_basetype" - ] + ], + "req_dex": 23, + "req_int": 23 }, { "name": "Velour Shoes", @@ -17657,7 +19886,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 38, + "req_int": 38 }, { "name": "Velvet Cap", @@ -17666,7 +19897,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 74 }, { "name": "Venerable Defender", @@ -17678,7 +19910,9 @@ "shield", "str_dex_armour", "str_dex_shield" - ] + ], + "req_str": 64, + "req_dex": 64 }, { "name": "Venerated Mantle", @@ -17687,7 +19921,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 48, + "req_int": 48 }, { "name": "Veridical Chain", @@ -17704,7 +19940,8 @@ "helmet", "int_armour", "karui_basetype" - ] + ], + "req_int": 64 }, { "name": "Veteran Sabatons", @@ -17713,7 +19950,9 @@ "boots", "default", "str_dex_armour" - ] + ], + "req_str": 47, + "req_dex": 47 }, { "name": "Vicious Talisman", @@ -17724,7 +19963,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 47, + "req_int": 34 }, { "name": "Vile Greataxe", @@ -17734,7 +19975,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 89, + "req_dex": 36 }, { "name": "Vile Robe", @@ -17743,7 +19986,8 @@ "body_armour", "default", "int_armour" - ] + ], + "req_int": 121 }, { "name": "Viper Cap", @@ -17753,7 +19997,8 @@ "dex_armour", "helmet", "vaal_basetype" - ] + ], + "req_dex": 54 }, { "name": "Visceral Quiver", @@ -17770,7 +20015,9 @@ "helmet", "maraketh_basetype", "str_dex_armour" - ] + ], + "req_str": 15, + "req_dex": 15 }, { "name": "Vitalic Ring", @@ -17798,7 +20045,8 @@ "no_physical_spell_mods", "onehand", "wand" - ] + ], + "req_int": 31 }, { "name": "Voltaic Staff", @@ -17821,7 +20069,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 53, + "req_int": 38 }, { "name": "Voodoo Focus", @@ -17831,7 +20081,8 @@ "focus", "int_armour", "vaal_basetype" - ] + ], + "req_int": 48 }, { "name": "Votive Raiment", @@ -17841,7 +20092,8 @@ "default", "int_armour", "vaal_basetype" - ] + ], + "req_int": 54 }, { "name": "Wanderer Armour", @@ -17850,7 +20102,9 @@ "body_armour", "default", "str_dex_armour" - ] + ], + "req_str": 45, + "req_dex": 45 }, { "name": "Wanderer Shoes", @@ -17859,7 +20113,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 47, + "req_int": 47 }, { "name": "War Spear", @@ -17870,7 +20126,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 14, + "req_dex": 31 }, { "name": "War Wraps", @@ -17879,7 +20137,9 @@ "default", "dex_int_armour", "gloves" - ] + ], + "req_dex": 44, + "req_int": 44 }, { "name": "Warded Helm", @@ -17888,7 +20148,9 @@ "default", "helmet", "str_dex_armour" - ] + ], + "req_str": 50, + "req_dex": 50 }, { "name": "Warden Bow", @@ -17900,7 +20162,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 23 }, { "name": "Warding Quarterstaff", @@ -17910,7 +20173,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 127, + "req_int": 50 }, { "name": "Warlock Leggings", @@ -17919,7 +20184,9 @@ "boots", "default", "str_int_armour" - ] + ], + "req_str": 56, + "req_int": 56 }, { "name": "Warlord Cuirass", @@ -17929,7 +20196,8 @@ "default", "karui_basetype", "str_armour" - ] + ], + "req_str": 121 }, { "name": "Warmonger Bow", @@ -17940,7 +20208,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_dex": 163 }, { "name": "Warmonger Greathelm", @@ -17949,7 +20218,8 @@ "default", "helmet", "str_armour" - ] + ], + "req_str": 91 }, { "name": "Warpick", @@ -17960,7 +20230,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_str": 41 }, { "name": "Warrior Greathelm", @@ -17970,7 +20241,8 @@ "helmet", "str_armour", "vaal_basetype" - ] + ], + "req_str": 52 }, { "name": "Waxed Jacket", @@ -17980,7 +20252,9 @@ "default", "dex_int_armour", "ezomyte_basetype" - ] + ], + "req_dex": 13, + "req_int": 13 }, { "name": "Waxing Quarterstaff", @@ -17990,7 +20264,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 75, + "req_int": 30 }, { "name": "Wayfarer Jacket", @@ -18000,7 +20276,9 @@ "default", "dex_int_armour", "maraketh_basetype" - ] + ], + "req_dex": 26, + "req_int": 26 }, { "name": "Wayfarer Shoes", @@ -18009,7 +20287,9 @@ "boots", "default", "dex_int_armour" - ] + ], + "req_dex": 34, + "req_int": 34 }, { "name": "Wayward Crest Shield", @@ -18021,7 +20301,9 @@ "shield", "str_int_armour", "str_int_shield" - ] + ], + "req_str": 36, + "req_int": 36 }, { "name": "Weaver Leggings", @@ -18031,7 +20313,9 @@ "default", "karui_basetype", "str_int_armour" - ] + ], + "req_str": 34, + "req_int": 34 }, { "name": "Whorl Focus", @@ -18041,7 +20325,8 @@ "focus", "int_armour", "karui_basetype" - ] + ], + "req_int": 73 }, { "name": "Wicker Tiara", @@ -18051,7 +20336,8 @@ "ezomyte_basetype", "helmet", "int_armour" - ] + ], + "req_int": 17 }, { "name": "Wide Belt", @@ -18071,7 +20357,9 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_str": 98, + "req_int": 72 }, { "name": "Wingbeat Talisman", @@ -18081,7 +20369,9 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 74, + "req_int": 52 }, { "name": "Winged Spear", @@ -18092,7 +20382,9 @@ "onehand", "spear", "weapon" - ] + ], + "req_str": 12, + "req_dex": 25 }, { "name": "Withered Wand", @@ -18115,7 +20407,8 @@ "one_hand_weapon", "onehand", "weapon" - ] + ], + "req_dex": 24 }, { "name": "Wolfskin Mantle", @@ -18124,7 +20417,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 }, { "name": "Wooden Buckler", @@ -18137,7 +20432,8 @@ "dex_shield", "ezomyte_basetype", "shield" - ] + ], + "req_dex": 10 }, { "name": "Wooden Club", @@ -18157,7 +20453,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 69 }, { "name": "Woven Cap", @@ -18166,7 +20463,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 91 }, { "name": "Woven Focus", @@ -18176,7 +20474,8 @@ "ezomyte_basetype", "focus", "int_armour" - ] + ], + "req_int": 11 }, { "name": "Wrapped Cap", @@ -18185,7 +20484,8 @@ "default", "dex_armour", "helmet" - ] + ], + "req_dex": 77 }, { "name": "Wrapped Greathelm", @@ -18195,7 +20495,8 @@ "helmet", "maraketh_basetype", "str_armour" - ] + ], + "req_str": 25 }, { "name": "Wrapped Quarterstaff", @@ -18216,7 +20517,8 @@ "default", "ezomyte_basetype", "int_armour" - ] + ], + "req_int": 17 }, { "name": "Wrath Sceptre", @@ -18225,7 +20527,8 @@ "default", "onehand", "sceptre" - ] + ], + "req_int": 87 }, { "name": "Wreath Focus", @@ -18234,7 +20537,8 @@ "default", "focus", "int_armour" - ] + ], + "req_int": 64 }, { "name": "Wyrm Quarterstaff", @@ -18244,7 +20548,9 @@ "twohand", "warstaff", "weapon" - ] + ], + "req_dex": 89, + "req_int": 36 }, { "name": "Wyrmscale Coat", @@ -18253,7 +20559,8 @@ "body_armour", "default", "dex_armour" - ] + ], + "req_dex": 121 }, { "name": "Zealot Bow", @@ -18265,7 +20572,8 @@ "twohand", "vaal_basetype", "weapon" - ] + ], + "req_dex": 70 }, { "name": "Zealot Crown", @@ -18274,7 +20582,9 @@ "default", "helmet", "str_int_armour" - ] + ], + "req_str": 40, + "req_int": 40 }, { "name": "Zealot Gauntlets", @@ -18283,7 +20593,9 @@ "default", "gloves", "str_dex_armour" - ] + ], + "req_str": 41, + "req_dex": 41 }, { "name": "Zealot Greathammer", @@ -18293,7 +20605,8 @@ "two_hand_weapon", "twohand", "weapon" - ] + ], + "req_str": 91 }, { "name": "Zenith Vestments", @@ -18302,7 +20615,9 @@ "body_armour", "default", "str_int_armour" - ] + ], + "req_str": 67, + "req_int": 67 } ] } diff --git a/tools/pobr-data-adapter/src/main.rs b/tools/pobr-data-adapter/src/main.rs index e2673494..ed579def 100644 --- a/tools/pobr-data-adapter/src/main.rs +++ b/tools/pobr-data-adapter/src/main.rs @@ -432,6 +432,9 @@ fn run(args: Args) -> Result { } bases.push(BaseItemDef { + req_str: 0, + req_dex: 0, + req_int: 0, id: raw.id, name: raw.name, item_class, diff --git a/tools/pobr-data-adapter/src/tree.rs b/tools/pobr-data-adapter/src/tree.rs index a264ee49..005d939e 100644 --- a/tools/pobr-data-adapter/src/tree.rs +++ b/tools/pobr-data-adapter/src/tree.rs @@ -152,6 +152,7 @@ pub fn run(args: TreeArgs) -> Result { // isSwitchable 变体由独立的 `--tree-variants ` 步骤回填 // (GGG data.json 不携带 options 变体)。 variants: Vec::new(), + apply_to_armour: false, }); } nodes.sort_by_key(|n| n.skill); diff --git a/tools/pobr-data-adapter/src/tree_anoints.rs b/tools/pobr-data-adapter/src/tree_anoints.rs index 59b9d098..9a896653 100644 --- a/tools/pobr-data-adapter/src/tree_anoints.rs +++ b/tools/pobr-data-adapter/src/tree_anoints.rs @@ -106,6 +106,7 @@ fn parse_missing_notables( connections: Vec::new(), ascendancy_id: None, variants: Vec::new(), + apply_to_armour: false, }); } if out.is_empty() { diff --git a/tools/pobr-data-adapter/src/tree_coords.rs b/tools/pobr-data-adapter/src/tree_coords.rs index bf093d46..861da27a 100644 --- a/tools/pobr-data-adapter/src/tree_coords.rs +++ b/tools/pobr-data-adapter/src/tree_coords.rs @@ -37,6 +37,9 @@ struct NodeOrbit { group: u32, orbit: u32, orbit_index: u32, + /// vendor `applyToArmour=true`(Smith of Kitava 身甲连接 notable 标记, + /// 同一 nodes 块顶层字段,顺路抽取回填 `PassiveNodeDef::apply_to_armour`)。 + apply_to_armour: bool, } /// 从 vendor `tree.lua` 解析出的布局常量、group 坐标与节点 orbit 槽位。 @@ -98,6 +101,11 @@ pub fn run(args: TreeCoordsArgs) -> Result { let mut missing = 0usize; let mut off_graph = 0usize; for node in &mut nodes { + // applyToArmour(Smith 身甲连接 notable)与坐标同源顺路回填(图外与否无关)。 + node.apply_to_armour = layout + .node_orbits + .get(&node.skill) + .is_some_and(|no| no.apply_to_armour); if !on_graph(node) { node.x = None; node.y = None; @@ -347,6 +355,7 @@ fn parse_node_orbits(block: &str) -> BTreeMap { group, orbit, orbit_index, + apply_to_armour: top.contains("applyToArmour=true"), }, ); } @@ -491,6 +500,7 @@ mod tests { group: 813, orbit: 0, orbit_index: 0, + apply_to_armour: false, }, ); let layout = TreeLayout { diff --git a/tools/pobr-data-adapter/src/tree_versions.rs b/tools/pobr-data-adapter/src/tree_versions.rs index a932bcf2..1bab70ce 100644 --- a/tools/pobr-data-adapter/src/tree_versions.rs +++ b/tools/pobr-data-adapter/src/tree_versions.rs @@ -103,6 +103,7 @@ fn parse_all_nodes(lua: &str) -> Result, String> { // `is_some()` 判「属于飞升」,语义等价。 ascendancy_id: string_field(node_block, "ascendancyName="), variants: Vec::new(), + apply_to_armour: false, }); } if out.is_empty() { diff --git a/tools/sync-pob-catalog/src/extract_base_overrides.lua b/tools/sync-pob-catalog/src/extract_base_overrides.lua index d3e11b05..e030996d 100644 --- a/tools/sync-pob-catalog/src/extract_base_overrides.lua +++ b/tools/sync-pob-catalog/src/extract_base_overrides.lua @@ -113,7 +113,15 @@ for name, base in pairs(itemBases) do tagsJson = "[" .. table.concat(keys, ",") .. "]" end end - if blockChance or spirit or reloadMs or charmBuff or tagsJson then + -- 基底属性需求(vendor `req = { str/dex/int/level }`;level 不入库—— + -- 消费方是装备需求快照 `RequirementsOn`,只吃三属性)。 + local reqStr, reqDex, reqInt = nil, nil, nil + if type(base.req) == "table" then + if type(base.req.str) == "number" and base.req.str > 0 then reqStr = base.req.str end + if type(base.req.dex) == "number" and base.req.dex > 0 then reqDex = base.req.dex end + if type(base.req.int) == "number" and base.req.int > 0 then reqInt = base.req.int end + end + if blockChance or spirit or reloadMs or charmBuff or tagsJson or reqStr or reqDex or reqInt then local parts = { '"name":"' .. jsonEscape(name) .. '"' } if blockChance then parts[#parts + 1] = '"block_chance":' .. jsonNum(blockChance) @@ -130,6 +138,15 @@ for name, base in pairs(itemBases) do if tagsJson then parts[#parts + 1] = '"tags":' .. tagsJson end + if reqStr then + parts[#parts + 1] = '"req_str":' .. string.format("%d", reqStr) + end + if reqDex then + parts[#parts + 1] = '"req_dex":' .. string.format("%d", reqDex) + end + if reqInt then + parts[#parts + 1] = '"req_int":' .. string.format("%d", reqInt) + end print("{" .. table.concat(parts, ",") .. "}") end end diff --git a/tools/sync-pob-catalog/src/extract_bases.rs b/tools/sync-pob-catalog/src/extract_bases.rs index 35bcf224..583a0056 100644 --- a/tools/sync-pob-catalog/src/extract_bases.rs +++ b/tools/sync-pob-catalog/src/extract_bases.rs @@ -189,6 +189,9 @@ mod tests { }; let entries = vec![ BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Omen Sceptre".to_string(), block_chance: None, spirit: Some(100), @@ -197,6 +200,9 @@ mod tests { tags: None, }, BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Crude Tower Shield".to_string(), block_chance: Some(26.0), spirit: None, @@ -205,6 +211,9 @@ mod tests { tags: None, }, BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Makeshift Crossbow".to_string(), block_chance: None, spirit: None, @@ -213,6 +222,9 @@ mod tests { tags: None, }, BaseItemOverrideEntry { + req_str: None, + req_dex: None, + req_int: None, name: "Ruby Charm".to_string(), block_chance: None, spirit: None, From 3ab51dce1e72e4a567ab81a1c9e39ba1855fb259 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:54:10 +0800 Subject: [PATCH 66/74] fix(ehp): average block over all four vendor categories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendor's EHP mitigation layer uses EffectiveAverageBlockChance = mean of (attack, projectile, spell, spell-projectile) block (CalcDefence.lua:1067), where SpellProjectileBlockChance = max(spellBlock+proj part, ProjectileBlock) (:1013). PoBR averaged only (attack + spell)/2, so shield builds with zero spell block were undercounted: smith 13.65% vs vendor 20.475%. calc_block already computed all four raw categories — exposed the two effective projectile variants through BlockResult/OutputTable and switched the EHP average to the 4-way mean. warrior-smith-of-kitava TotalEHP 91338 -> 99177 vs golden 99167.69 (0.92x -> 1.00x) and warrior-titan TotalEHP 54483 -> 58898 vs 58904.30 (0.92x -> 1.00x). Def 25-col 442 -> 444 @5% / 444 @10%. --- crates/pobr-core/src/calc/defence_panels.rs | 12 ++++++++++++ crates/pobr-core/src/calc/ehp.rs | 14 ++++++++++---- crates/pobr-core/src/calc/output.rs | 8 ++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/pobr-core/src/calc/defence_panels.rs b/crates/pobr-core/src/calc/defence_panels.rs index 83fa71ff..8a6f310b 100644 --- a/crates/pobr-core/src/calc/defence_panels.rs +++ b/crates/pobr-core/src/calc/defence_panels.rs @@ -46,6 +46,10 @@ pub struct BlockResult { pub effective_block_chance: f64, /// 有效法术格挡(%)。 pub effective_spell_block_chance: f64, + /// 有效投射物攻击格挡(%)。EHP 平均格挡 = 四分型均值(vendor :1067)。 + pub effective_projectile_block_chance: f64, + /// 有效法术投射物格挡(%)。 + pub effective_spell_projectile_block_chance: f64, /// 格挡承伤比例(%;被格挡命中仍承受的伤害份额 = Σ BASE BlockEffect, /// vendor `DamageTakenOnBlock`;0 = 完全格挡)。 pub block_effect_taken_pct: f64, @@ -186,6 +190,11 @@ pub fn calc_block(db: &ModDb, cfg: &CalcConfig) -> BlockResult { spell_projectile_block_chance: round(spell_projectile), effective_block_chance: effective(block, "BlockChance"), effective_spell_block_chance: effective(spell, "SpellBlockChance"), + effective_projectile_block_chance: effective(projectile, "ProjectileBlockChance"), + effective_spell_projectile_block_chance: effective( + spell_projectile, + "SpellProjectileBlockChance", + ), // :1054-1058 承伤份额(clamp 到 [0,100],超额防住不为负)。 block_effect_taken_pct: db .sum(ModType::Base, cfg, &[ModName::from("BlockEffect")]) @@ -370,6 +379,9 @@ pub fn fill_defence_panels(env: &mut Env, keystones: &crate::rules::DefenceKeyst env.player.output.spell_block_chance_max = block.spell_block_chance_max; env.player.output.effective_block_chance = block.effective_block_chance; env.player.output.effective_spell_block_chance = block.effective_spell_block_chance; + env.player.output.effective_projectile_block_chance = block.effective_projectile_block_chance; + env.player.output.effective_spell_projectile_block_chance = + block.effective_spell_projectile_block_chance; env.player.output.block_effect = block.block_effect_taken_pct; // --- Spirit 池本值 + 未预留余量(CalcDefence.lua:73-126 / :330-337)--- diff --git a/crates/pobr-core/src/calc/ehp.rs b/crates/pobr-core/src/calc/ehp.rs index 349fe1bd..11a45020 100644 --- a/crates/pobr-core/src/calc/ehp.rs +++ b/crates/pobr-core/src/calc/ehp.rs @@ -1134,10 +1134,16 @@ pub fn fill_ehp_pob2( let n_hits = number_of_hits_to_die(&taken_hit, &pools, &ctx, ¶ms); // ---- mitigation 概率层(:3155-3247)---- - // 平均格挡:vendor :1052 为四分型均值;Projectile 分型未进 OutputTable - // (无投射物专项词条时与基础分型同值),以 (攻击+法术)/2 等价。 - let avg_block_frac = - (out.effective_block_chance + out.effective_spell_block_chance) / 2.0 / 100.0; + // 平均格挡 = 四分型均值(vendor :1067 EffectiveAverageBlockChance)。旧 + // 二分型均值把 SpellProjectileBlock = max(spellBlock, projBlock)(:1013) + // 漏掉——盾 build(spellBlock 0、projBlock = block)被低估:smith 13.65 + // vs vendor 20.475(TotalEHP 0.92x 根因)。 + let avg_block_frac = (out.effective_block_chance + + out.effective_projectile_block_chance + + out.effective_spell_block_chance + + out.effective_spell_projectile_block_chance) + / 4.0 + / 100.0; // vendor BlockEffect(防住份额%)= 100 − ΣBASE = 100 − out.block_effect(承伤份额)。 let block_effect_mult = 1.0 - avg_block_frac * (100.0 - out.block_effect) / 100.0; // :3195 deflect 乘数(chance<100 口径;=100 时已折入 afterReductionMulti)。 diff --git a/crates/pobr-core/src/calc/output.rs b/crates/pobr-core/src/calc/output.rs index ba1d70d9..48c059bc 100644 --- a/crates/pobr-core/src/calc/output.rs +++ b/crates/pobr-core/src/calc/output.rs @@ -169,6 +169,12 @@ pub struct OutputTable { pub effective_block_chance: f64, /// 有效法术格挡几率(%;PoB2 `EffectiveSpellBlockChance`)。 pub effective_spell_block_chance: f64, + /// 有效投射物攻击格挡几率(%;PoB2 `EffectiveProjectileBlockChance`)。 + /// EHP 平均格挡 = 四分型均值(vendor CalcDefence.lua:1067)。 + pub effective_projectile_block_chance: f64, + /// 有效法术投射物格挡几率(%;PoB2 `EffectiveSpellProjectileBlockChance`, + /// vendor :1013 与 ProjectileBlock 取 max)。 + pub effective_spell_projectile_block_chance: f64, /// 格挡承伤比例(%;被格挡命中仍承受的伤害份额,PoB2 `BlockEffect`, /// ModParser.lua:2479;0 = 完全格挡)。 pub block_effect: f64, @@ -451,6 +457,8 @@ impl Default for OutputTable { spell_block_chance_max: 0.0, effective_block_chance: 0.0, effective_spell_block_chance: 0.0, + effective_projectile_block_chance: 0.0, + effective_spell_projectile_block_chance: 0.0, block_effect: 0.0, deflection_rating: 0.0, deflect_chance: 0.0, From 0cd02b07a14fa908256b36d4e30b970144f64e83 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:54:17 +0800 Subject: [PATCH 67/74] test(parity): raise defence baselines after #14 long-tail triage Def 25-col 431 -> 444 @5% (439 -> 444 @10%), core-8 142 -> 144/144 (100%). Six clusters closed; the 6 remaining @5% misses are all mercenary-tactician-wolf-pack (task #13's lane). Reason chain in the baseline comments. --- .../pobr-build/tests/parity/ninja_parity.rs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index dc1fd8f4..24e6b5a7 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -541,7 +541,11 @@ fn compute_tallies(verbose: bool) -> (Tally, Tally, Tally, Tally, Vec) { // Atziri's Communion 的 Spirit→Life 保留转换(LifeReservePercentPerSpirit, // vendor CalcDefence.lua:248-254)接入后双列翻正;abyssal-lich(同戴 Communion) // SpiritUnres inf→1.00x 同根。见 buffs.rs spirit_reservation_modifiers 转换分支。 -const BASELINE_DEF_CORE_HIT5: usize = 142; // 存量 #7-3/4(charm-guard 摘除 + wolf-pack Life/Armour 收敛 + gemling 池名归一)后 142/144(Communion 140(ItemES 138;Barrier-Life 136;Mageblood 135;迁移基线 118;0.5.0=139) +// **#14 防御长尾分诊重记(+2 core-8 142→144/144 = 100%)**:abyssal-lich Life +// (LifeConvertToEnergyShield 池扣减,0_5 树 Enhanced Barrier)+ smith Armour +// (connected-notable multiplier + StrRequirements 快照)翻正。详见 #14 各修复 +// commit 与 docs/adapting-to-0.5.4b.md §#14。 +const BASELINE_DEF_CORE_HIT5: usize = 144; // #14 长尾分诊后 144/144(存量 #7-3/4 142;Communion 140(ItemES 138;Barrier-Life 136;Mageblood 135;迁移基线 118;0.5.0=139) // **per-socket-filled 修复重记(+1 @5%/@10%)**:gemling-legionnaire 身甲 Morior Invictus // `+14 to Spirit per Socket filled`(×5 socket)经 `RunesSocketedIn{SlotName}` Multiplier // 接入 → Spirit 180→250(0.72x→1.00x,翻正)。详见 collect.rs::filter_parseable 闸门 + @@ -656,8 +660,18 @@ const BASELINE_DEF_CORE_HIT5: usize = 142; // 存量 #7-3/4(charm-guard 摘除 // 0.72x→0.80x / TotalEHP 0.74x→0.83x(池侧 3817 vs oracle 3826.67 = 0.9975 已 // 闭合,余量 = per-type taken 乘子侧 ~10% 均匀缺口 + Mana 761.2 vs 770,均与 // companion 层无关)。 -const BASELINE_DEF_HIT5: usize = 431; // #12 companion 后 431/450(存量 #7-3/4 425:ritualist 6 格 + gemling 9 格 + wolf-pack Life/Armour 精确翻正,wolf-pack MaxHit 族随 charm-guard 掩蔽摘除移出;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) -const BASELINE_DEF_HIT10: usize = 439; // #12 companion 后 439/450(Communion+Voices 434;Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) +// **#14 防御长尾分诊重记(+13 @5% 431→444 / +5 @10% 439→444)**:五簇闭合—— +// ① PhysDR 取整(vendor :2402 armourReduction 整数变体)ember/deadeye 2 格; +// ② Life 池 ConvertTo 扣减(CalcDefence.lua:92)abyssal-lich Life/LifeUnres 2 格; +// ③ Blasphemy per-curse 并入 baseFlat 单次 round(:229-239)essence-drain +// SpiritUnres 1 格;④ altQualityStats 通道(GemlingQuality 门控, +// CalcTools.lua:147-152)gemling SpiritUnres 1 格;⑤ Smith connected-notable +// multiplier + StrRequirementsOn 快照(CalcSetup.lua:840/CalcPerform. +// lua:1848-1857)smith Armour+4×MaxHit+TotalEHP 等 5 格;⑥ EHP 平均格挡改 +// 四分型均值(:1067,SpellProjectileBlock=max(spell,proj) 不再漏)smith+titan +// TotalEHP 2 格。剩余 6 格全部 = wolf-pack(#13 领地)。 +const BASELINE_DEF_HIT5: usize = 444; // #14 长尾分诊后 444/450(#12 companion 431;存量 #7-3/4 425;Communion+Voices 413(ArmourAppliesTo 410;Refraction 407;ItemES 405;Barrier-Life 401;Mageblood 393;迁移基线 343;0.5.0=415) +const BASELINE_DEF_HIT10: usize = 444; // #14 长尾分诊后 444/450(#12 companion 439;Communion+Voices 434;Refraction 429;ItemES 428;Barrier-Life 425;Mageblood 417;迁移基线 361;0.5.0=432) // **附加授予效果展开重记(+3 @10%)**:gem 的 additionalGrantedEffectId1..N // (overlay/gem_effects.json 外键,如三 banner 的 buff 侧效果——主位是预留侧 // ReservationPlayer、buff 侧 BannerPlayer(Aura)在附加位)在 buff_skill_specs From 6e78f2d0418cdba6cb1de7d2f718b749bba7d792 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 20:55:06 +0800 Subject: [PATCH 68/74] docs: record #14 defensive long-tail triage table and fixes --- docs/adapting-to-0.5.4b.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 5de25544..28d85802 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -517,6 +517,37 @@ not the companion layer. Baselines: def 425→431 @5%, 434→439 @10%; off/dot/ core-8 unchanged; 18-build per-cell diff shows only twister and wolf-pack moving. +**#14 — defensive 25-col long-tail triage. ✅ DONE (six clusters closed; +def 25-col 431→444 @5% / 439→444 @10%, core-8 142→144/144 = 100%).** + +First full per-cell triage of the 19 cells outside the 5% band (never done +before). Full table at triage time (`~` = inside 10%, outside 5%; excluded = +wolf-pack ×6 + titan ×1, task #13's lane): + +| build × col | ratio | root cause | vintage | status | +|---|---|---|---|---| +| druid-ember-fusillade PhysDR | 1.06x ~ | armour-DR **integer rounding**: vendor `TakenHit` chain uses `calcs.armourReduction` = `round(armourReductionF)` (CalcDefence.lua:2402, Common.lua round = floor(x+0.5)); PoBR used the fractional variant everywhere. Golden PhysDR is always integer for this reason. | pre-existing (only visible at small DR) | ✅ fixed (699e9e3) | +| ranger-deadeye PhysDR | 1.20x | same | same | ✅ fixed (699e9e3) | +| abyssal-lich Life | 1.05x ~ | **Life→ES pool conversion** never deducted: 0_5-tree "Enhanced Barrier" (node 44299) gained `5% of Maximum Life Converted to Energy Shield` in 0.5.4b tree data; parser produced `LifeConvertToEnergyShield` BASE 5, defence matrix credited the ES side, but the pool-side `(1−conv/100)` (CalcDefence.lua:92) was explicitly deferred and never landed. Oracle: 1511×0.95×1.18 = 1693.83 vs golden 1694 exact. | 0.5.4b (node stats changed on 0_5 tree) | ✅ fixed (8b742d8) | +| abyssal-lich LifeUnres | 1.05x ~ | dependent (reservation is %-based) | — | ✅ same commit | +| essence-drain SpiritUnres | 0.86x (6 vs 7) | **Blasphemy per-curse rounding** drifted in 0.5.4b vendor: per-curse flat now folds into `baseFlat` *before* one efficiency-scaled round (`round(180/1.1)=164`, CalcDefence.lua:229-239); PoBR kept the old per-instance `round(60/1.1)=55×3=165`. Oracle `spiritReservedBreakdown` pins 164. | 0.5.4b (vendor code change) | ✅ fixed (fff5b2e) | +| gemling SpiritUnres | 3.33x (−60 vs −18) | **altQualityStats channel missing**: Gemling's "Advanced Thaumaturgy" (GemlingQuality flag) makes gems apply their alt quality stats (CalcTools.lua:147-152). Mirage Archer (alt `base_reservation_efficiency_+%` ×2 → 62% @q31) and Eternal Rage (alt `base_spirit_reservation_efficiency_+%` ×0.75 → 23%) reservation efficiencies were lost: 60→37, 100→81 (Δ42 = the whole gap). extract-lua gem-quality never transcribed `altQualityStats`. | pre-existing (data channel gap) | ✅ fixed (2896807) | +| smith Armour | 0.76x | **two dead provisioning inputs** (both mods parsed): ① `Multiplier:AllocatedConnectedNotable` — count of allocated tree.lua `applyToArmour=true` notables (12 exist, smith allocates 8 → +200×8 flat; CalcSetup.lua:840-841); the flag was absent from PoBR tree data. ② `StrRequirementsOn` PercentStat stats (CalcPerform.lua:1848-1857) — base-item attribute requirements absent from PoBR data (.dat bundle unavailable) → extended extract-bases vendor channel with `req = {str/dex/int}`. Oracle: Tree:9988 BASE 1600 + Tree:59589 152/120/161. Result 49096.16 vs 49096 exact. **Note**: the old "vendor↔PoBR tree skill mapping deadlock" memory was stale — `applyToArmour` is plain per-node vendor tree data, no mapping needed. | pre-existing | ✅ fixed (22e1b79) | +| smith PhysMaxHit / FireMaxHit / ColdMaxHit / LightMaxHit | 0.91-0.93x ~ | dependent on Armour | — | ✅ same commit (all 1.00x exact) | +| smith TotalEHP | 0.85→0.92x ~ | after Armour fix, residual = **EHP average block used 2 categories instead of 4**: vendor `EffectiveAverageBlockChance = (attack+projectile+spell+spellProjectile)/4` (CalcDefence.lua:1067) with `SpellProjectileBlock = max(spellBlock…, ProjectileBlock)` (:1013). Shield builds with 0 spell block: PoBR 13.65% vs vendor 20.475%. | pre-existing | ✅ fixed (3ab51dc), 99177 vs 99167.69 | +| titan TotalEHP | 0.92x ~ | same block-average root (shared consumption point; cell itself is #13's lane but the generic fix closes it) | pre-existing | ✅ same commit, 58898 vs 58904.30 | + +Remaining after #14: the 6 wolf-pack cells (TotalEHP 0.83x, 4×ele/phys +MaxHit 0.90x, ChaosMaxHit 0.80x) — task #13's territory (per-type taken-mult +~10% uniform gap, see #12 notes). + +Deliberate ceilings (marked in code): pool-conv applies to the whole +Maximum BASE sum (vendor exempts `Extra`; no dual-conversion build +in corpus); `reqMult` for attribute requirements held at 1 (no +reduced-requirements + armour-from-str build in corpus); alt quality stats +consumed only by the reservation-efficiency path so far (accessor is +general; offence-side alt stats wire up when a fixture demands). + ## Tooling - `examples/demo-bd-test/tools/recapture_golden.py` — refresh fixture goldens From bb4b220ae23bb99dc9b61aa152e45771ea028563 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 22:28:21 +0800 Subject: [PATCH 69/74] fix(tree): radius-jewel grants no longer land on inactive weapon-set nodes Reverts the 75a348e mechanism (inactive weapon-set nodes merged back into the radius-jewel grant geometry). That change was based on a misreading: in vendor, EVERY mod on an allocMode != 0 node - the node's own stats and radius-jewel grants alike - gets 'Condition: WeaponSet' appended (CalcSetup.lua:222-223; the jewel-source branch :224-227 only fires for allocMode-0 nodes), so grants on inactive-set notables are dead. Oracle critModList Tabulate on gemling pins exactly one '7 @ Tree:32763' (INC total 71 -> CritChance 5 x 1.71 = 8.55, matching the 0.5.4b golden). PoBR previously granted 6x +7 -> 10.30 (1.20x overshoot). gemling: CritChance 1.20x -> 1.00x exact; the same jewel's Small-node grants shrink too -> AvgDamage/TotalDPS 1.04x -> 0.97x (in band); TotalDotDPS 1.10x -> 0.95x (in band; residual is purely downstream of the pre-existing AvgDamage under-estimate via ignite chance x stack potential, 0.9754^2 = 0.951 exact). Other 17 builds unaffected. --- crates/pobr-build/src/build.rs | 17 +------ .../src/calc_orchestrator/collect.rs | 20 ++++---- crates/pobr-build/src/xml_build.rs | 46 ++++++------------- 3 files changed, 24 insertions(+), 59 deletions(-) diff --git a/crates/pobr-build/src/build.rs b/crates/pobr-build/src/build.rs index 3e31489c..93732a10 100644 --- a/crates/pobr-build/src/build.rs +++ b/crates/pobr-build/src/build.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use pobr_data::build_config::ViewMode; use pobr_data::item::{EquipmentSlot, Item}; -use pobr_data::passive_tree::{NodeId, PassiveTreeSpec}; +use pobr_data::passive_tree::PassiveTreeSpec; use crate::build_config::BuildConfig; @@ -219,15 +219,6 @@ pub struct Build { /// `jewels` 注入珠宝**自身**的全局词条,本列表额外按半径几何把 `also grant` 展开为 /// 「半径内已分配对应种类节点数 × 授予」的全局 mod(见 `calc_orchestrator`)。 pub radius_jewels: Vec, - /// 非激活武器组专属点(`` 中属于未激活组的已分配节点)。 - /// - /// 这些节点的**自身** mod 已在解析层 masking(不进 [`Self::tree`] 的 `allocated_nodes`, - /// 等价 PoB2 `Condition: WeaponSet` 门控);但 PoB2 仍把它们留在 `allocNodes`, - /// 因此**范围珠宝授予**(`... in Radius also grant`)按 PoB2 语义仍会落到这些节点上 - /// (CalcSetup.lua:209-228,授予源=jewel、按 jewel allocMode 门控)。此列表供 - /// `radius_jewel_expansions` 在 radius 几何里并回完整已分配集,复刻该行为。 - /// 无武器组切换的 build 此列表为空,对计算零影响。 - pub inactive_weapon_set_nodes: Vec, /// **激活态**药剂/护符的「槽名 + 物品」(M3-T4,蓝图 §7.2-2): /// `("Flask 1"|"Charm 1..3", Item)`,XML 文档序,仅 `active="true"` 的槽进入 /// (vendor CalcSetup.lua:1014-1028 `slot.active` 门控)。槽名供 flask/charm @@ -298,12 +289,6 @@ impl Build { self } - /// 设定非激活武器组专属点列表(见 [`Self::inactive_weapon_set_nodes`]),返回新副本。 - pub fn with_inactive_weapon_set_nodes(mut self, nodes: Vec) -> Self { - self.inactive_weapon_set_nodes = nodes; - self - } - /// 设定激活态药剂/护符的槽名保留列表(见 [`Self::utility_slots`]),返回新副本。 pub fn with_utility_slots(mut self, slots: Vec<(String, Item)>) -> Self { self.utility_slots = slots; diff --git a/crates/pobr-build/src/calc_orchestrator/collect.rs b/crates/pobr-build/src/calc_orchestrator/collect.rs index 562a888d..03a7dc0c 100644 --- a/crates/pobr-build/src/calc_orchestrator/collect.rs +++ b/crates/pobr-build/src/calc_orchestrator/collect.rs @@ -309,17 +309,15 @@ pub(crate) fn radius_jewel_expansions<'a>( } // 已分配节点集合(含种类)。坐标取自 data.passive_nodes(树数据回填的 x/y)。 // - // 并入 `inactive_weapon_set_nodes`:非激活武器组专属点的**自身** mod 虽已 masking, - // 但 PoB2 仍把它们留在 allocNodes(CalcSetup.lua:209-228),范围珠宝授予照样落上去。 - // radius 几何因此须按完整已分配集筛 in-radius 节点(gemling crit jewel 实测:6 个 - // in-radius notable 中 5 个在非激活组,缺它们会把 +7×6 误算成 +7×1)。 - let allocated: std::collections::HashSet = build - .tree - .allocated_nodes - .iter() - .chain(build.inactive_weapon_set_nodes.iter()) - .map(|n| n.0) - .collect(); + // 只取**激活武器集**的已分配节点:PoB2 虽把非激活集专属点留在 allocNodes,且范围 + // 珠宝授予也会写进它们的 modList,但节点上的**每条** mod(含珠宝授予)都会被追加 + // `Condition: WeaponSet`(CalcSetup.lua:222-223,节点自身 allocMode 优先于 + // 珠宝来源的 :224-227 分支)——非激活集节点上的授予净效果为零。oracle Tabulate + // 实证(gemling crit jewel):6 个 in-radius notable 只有激活集的 1 个贡献 +7 + // (critModList 仅一条 `7 @ Tree:32763`,CritChance 8.55)。PoBR 解析层已按激活集 + // 剔除非激活专属点,这里直接用 `tree.allocated_nodes` 即等价。 + let allocated: std::collections::HashSet = + build.tree.allocated_nodes.iter().map(|n| n.0).collect(); // 位置表:socket 自身 + 全部已分配节点(候选只在已分配集合中筛)。 let mut positions: std::collections::HashMap = diff --git a/crates/pobr-build/src/xml_build.rs b/crates/pobr-build/src/xml_build.rs index 14ed6e97..21c26bf3 100644 --- a/crates/pobr-build/src/xml_build.rs +++ b/crates/pobr-build/src/xml_build.rs @@ -73,7 +73,6 @@ pub fn parse_build(xml: &str) -> Result { let use_second_weapon_set = parse_active_item_set(xml)?.3; let ParsedPassives { allocated: allocated_nodes, - inactive_weapon_set: inactive_weapon_set_nodes, tree_version, } = parse_passive_nodes(xml, use_second_weapon_set)?; let allocated_set: std::collections::HashSet = @@ -110,9 +109,6 @@ pub fn parse_build(xml: &str) -> Result { if !radius_jewels.is_empty() { build = build.with_radius_jewels(radius_jewels); } - if !inactive_weapon_set_nodes.is_empty() { - build = build.with_inactive_weapon_set_nodes(inactive_weapon_set_nodes); - } if !flask_charms.is_empty() { build = build.with_utility_slots(flask_charms); } @@ -470,25 +466,18 @@ struct SpecNodes { /// `activeSpec` 为 1-based 索引;越界 / 缺失时取首个 ``。无 `` 返回空。 /// /// 武器集语义(PoB2 CalcSetup.lua:209-233 / :791-792):武器集专属点 -/// (`allocMode = 1|2`)的全部**自身** mod 追加 `Condition: WeaponSet`,而该条件 -/// flag 只对当前激活武器集置真(`useSecondWeaponSet` ? 2 : 1)——净效果是非激活集 -/// 专属点的**自身词条**整体不生效。PoBR 在解析层等价实现:从已分配节点中剔除非激活集 -/// 的专属点(其自身 mod 收集随之关闭,与 PoB2 条件门控等价)。 -/// -/// ⚠️ 但 PoB2 **不**把这些节点从 `allocNodes` 移除——它们仍是已分配节点,只是自身 mod -/// 被条件门控(CalcSetup.lua:209-228)。因此**范围珠宝授予**(`... in Radius also -/// grant`,源=jewel 而非节点,按 jewel 的 allocMode 门控而非节点的,:224-228)仍会落到 -/// 这些非激活集 notable 上。PoBR 把剔除掉的节点单独回传(返回值 `.1`),供 -/// [`crate::calc_orchestrator::collect::radius_jewel_expansions`] 在 radius 几何里并回, -/// 以复刻 PoB2「非激活集节点仍计入 radius 授予」的行为(gemling crit jewel 实测)。 +/// (`allocMode = 1|2`)节点上的**每条** mod——含自身词条与范围珠宝授予——都被追加 +/// `Condition: WeaponSet`(节点自身 allocMode 优先,CalcSetup.lua:222-223;珠宝 +/// 来源门控的 :224-227 分支仅对 allocMode=0 节点生效),而该条件 flag 只对当前激活 +/// 武器集置真(`useSecondWeaponSet` ? 2 : 1)——净效果是非激活集专属点上的全部词条 +/// **整体不生效**。PoBR 在解析层等价实现:从已分配节点中剔除非激活集的专属点 +/// (mod 收集 / 范围珠宝授予计数 / per-X 倍率均随之一致;oracle 实证见 +/// `collect::radius_jewel_expansions`)。 /// -/// 返回 `(激活已分配节点, 被剔除的非激活集节点, tree_version)`。 /// [`parse_passive_nodes`] 的解析结果。 struct ParsedPassives { /// 激活已分配节点(自身 mod 参与计算)。 allocated: Vec, - /// 被剔除的非激活武器组专属点(自身 mod 已 masking,但仍计入范围珠宝授予几何)。 - inactive_weapon_set: Vec, tree_version: Option, } @@ -540,7 +529,6 @@ fn parse_passive_nodes(xml: &str, use_second_weapon_set: bool) -> Result Result = spec.weapon_set [if use_second_weapon_set { 0 } else { 1 }] .iter() .copied() .collect(); - let mut nodes = Vec::with_capacity(spec.nodes.len()); - let mut masked = Vec::new(); - for n in spec.nodes { - if inactive.contains(&n) { - masked.push(n); - } else { - nodes.push(n); - } - } + let allocated = spec + .nodes + .into_iter() + .filter(|n| !inactive.contains(n)) + .collect(); Ok(ParsedPassives { - allocated: nodes, - inactive_weapon_set: masked, + allocated, tree_version, }) } From 14cecb60ec85133e90035b0b381ae8875d64eb75 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 22:28:31 +0800 Subject: [PATCH 70/74] fix(calc): honor non-damage main skill in designated group; untyped skills only eat SkillSpeed wolf-pack Speed read 1.43 vs vendor 1.00. Two stacked causes: 1. Main-skill selection: Wolf Pack (Minion+Companion, neither Attack nor Spell) failed the is-damage-skill filter, so mainSocketGroup=4 yielded nothing and the fallback scan hijacked the main skill to the Blasphemy group's Temporal Chains (0.7s cast -> Speed 1.43). Vendor's socketGroupSkillList has no damage filter. Fix: when pick_group_main_skill misses on the DESIGNATED group, fall back to the mainActiveSkill-chosen active gem (pick_group_chosen_active; Meta shells still excluded, cross-group scan still damage-only). 2. Speed bucket: with Wolf Pack as main (castTime 1s), the 'untyped skill eats all three speed buckets' fallback consumed the weapon's '12% reduced Attack Speed' -> 0.88. Vendor mod-matching requires cfg to carry ModFlag.Attack/Cast for those mods; untyped skills eat neither. speed_names_for now returns only SkillSpeed for non-attack/non-spell. Oracle mainOutput pins Speed=1, Time=1, CastRate=1 (statSet 'Minion Info', player TotalDPS 0). wolf-pack Speed now 1.00x; all other builds' main skills are attack/spell typed - no behavior change. Also adds a POBR_DBG_SPEED probe (main skill id + speed factor dump). --- .../pobr-build/src/calc_orchestrator/mod.rs | 5 +- .../src/calc_orchestrator/skill_resolve.rs | 95 ++++++++++++++----- crates/pobr-core/src/calc/offence.rs | 11 +++ crates/pobr-core/src/calc/skill_use_time.rs | 10 +- 4 files changed, 94 insertions(+), 27 deletions(-) diff --git a/crates/pobr-build/src/calc_orchestrator/mod.rs b/crates/pobr-build/src/calc_orchestrator/mod.rs index 0b65a9d1..49a389c3 100644 --- a/crates/pobr-build/src/calc_orchestrator/mod.rs +++ b/crates/pobr-build/src/calc_orchestrator/mod.rs @@ -913,10 +913,13 @@ fn stage_build_cfg(ctx: &mut StageCtx<'_>) { /// `CalculationSession::new`)。 fn stage_weapon_bases(ctx: &mut StageCtx<'_>) { let (build, data) = (ctx.build, ctx.data); - if let Some((skill, _, _)) = &ctx.main_skill + if let Some((skill, _, skill_id)) = &ctx.main_skill && let Some(use_time) = skill.use_time_s && use_time > 0.0 { + if std::env::var("POBR_DBG_SPEED").is_ok() { + eprintln!("[POBR_DBG_SPEED] main skill_id={skill_id} use_time={use_time}"); + } ctx.base_input.base_action_rate = 1.0 / use_time; } diff --git a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs index 1e722a1e..c5673308 100644 --- a/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs +++ b/crates/pobr-build/src/calc_orchestrator/skill_resolve.rs @@ -230,28 +230,10 @@ pub(crate) fn pick_group_main_skill<'b>( build_data: &'b BuildData, group: &'b SocketGroup, ) -> Option<(&'b str, u32, Option)> { - // 非辅助宝石列表(meta 壳算入),与 PoB socketGroupSkillList 一致。`gem_skills` 存的是 - // 授予效果 id,故经 granted_effects.is_support 判定(未知效果按非 support 处理,宁可保留)。 - let actives: Vec<&crate::build::GemSkillRef> = group - .gem_skills - .iter() - .filter(|g| { - !build_data - .granted_effects - .get(&g.skill_id) - .map(|e| e.is_support) - .unwrap_or(false) - }) - .collect(); + let actives = group_active_gems(build_data, group); if !actives.is_empty() { - // mainActiveSkill(1-based)→ 0-based,越界 clamp 到末项。 - let idx = group - .main_active_skill - .unwrap_or(1) - .saturating_sub(1) - .min(actives.len() - 1); - let chosen = actives[idx]; + let chosen = group_chosen_active(group, &actives); // 指定项即伤害技能 → 直接用;否则(meta 壳等)穿透到组内首个伤害技能。 if is_damage_skill(build_data, &chosen.skill_id) { @@ -295,6 +277,71 @@ pub(crate) fn pick_group_main_skill<'b>( .map(|id| (id, group.active_gem_level.unwrap_or(1), None)) } +/// 组内非辅助宝石列表(meta 壳算入),与 PoB `socketGroupSkillList` 一致。`gem_skills` +/// 存的是授予效果 id,故经 granted_effects.is_support 判定(未知效果按非 support 处理, +/// 宁可保留)。 +fn group_active_gems<'b>( + build_data: &BuildData, + group: &'b SocketGroup, +) -> Vec<&'b crate::build::GemSkillRef> { + group + .gem_skills + .iter() + .filter(|g| { + !build_data + .granted_effects + .get(&g.skill_id) + .map(|e| e.is_support) + .unwrap_or(false) + }) + .collect() +} + +/// `mainActiveSkill`(1-based,缺省 1,越界 clamp 到末项)在非辅助宝石列表中的选中项。 +fn group_chosen_active<'b>( + group: &SocketGroup, + actives: &[&'b crate::build::GemSkillRef], +) -> &'b crate::build::GemSkillRef { + let idx = group + .main_active_skill + .unwrap_or(1) + .saturating_sub(1) + .min(actives.len() - 1); + actives[idx] +} + +/// 组内按 `mainActiveSkill` 选中的**任意**主动技能(非 support、非 Meta 壳), +/// 不要求攻击/法术标签。 +/// +/// vendor 语义:`socketGroupSkillList` 收录组内全部非辅助宝石,`mainActiveSkill` +/// 直接选中——**没有**「必须是伤害技能」的过滤(CalcSetup.lua socketGroupSkillList +/// 段)。伙伴/召唤类主技能(如 Wolf Pack:`Minion`+`Companion`,非 Attack 非 +/// Spell)在 vendor 里照常作为 mainActiveSkill 计算(castTime 基底、Speed=1/castTime)。 +/// PoBR 的 [`pick_group_main_skill`] 出于 meta 壳穿透保留伤害技能偏好;本函数作为 +/// **显式指定主组**(`mainSocketGroup`)落空后的兜底,避免回退扫描把主技能劫持到 +/// 其它组(wolf-pack 实测:曾错落到 Blasphemy 组的 Temporal Chains, +/// Speed 1.43 vs vendor 1.00)。仅供指定主组分支消费;回退扫描仍只找伤害技能组。 +pub(crate) fn pick_group_chosen_active<'b>( + build_data: &'b BuildData, + group: &'b SocketGroup, +) -> Option<(&'b str, u32, Option)> { + let actives = group_active_gems(build_data, group); + if actives.is_empty() { + return None; + } + let chosen = group_chosen_active(group, &actives); + // Meta 壳无独立施放参数,仍排除(穿透逻辑归 pick_group_main_skill)。 + let effect = build_data.granted_effects.get(&chosen.skill_id)?; + if effect.skill_types.iter().any(|t| t == "Meta") { + return None; + } + Some(( + chosen.skill_id.as_str(), + chosen.gem_level, + chosen.stat_set_index, + )) +} + /// 解析 build 的主技能分等级参数:优先用 PoB 指定的主技能组(`mainSocketGroup`,1-based)+ /// 组内 `mainActiveSkill` 选中真正的伤害技能(跳过 support 与 meta/触发壳),用其授予效果 /// id + 宝石等级查 [`BuildData::resolve_skill_level`]。 @@ -309,9 +356,12 @@ pub(crate) fn resolve_main_skill<'b>( data: &'b BuildData, ) -> Option<(ResolvedSkillLevel, &'b SocketGroup, &'b str)> { // 优先用 PoB 指定的主技能组(`mainSocketGroup`,1-based)+ 组内 mainActiveSkill。 + // 组内无伤害技能候选时兜底用选中的任意主动技能(vendor 语义无伤害过滤, + // 见 [`pick_group_chosen_active`];避免伙伴/召唤主组被回退扫描劫持到其它组)。 if let Some(n) = build.main_socket_group && let Some(group) = build.socket_groups.get(n.saturating_sub(1)) - && let Some((skill_id, level, set_index)) = pick_group_main_skill(data, group) + && let Some((skill_id, level, set_index)) = + pick_group_main_skill(data, group).or_else(|| pick_group_chosen_active(data, group)) && let Some(resolved) = resolve_skill_level_with_gem_bonus(build, data, skill_id, level, set_index) { @@ -338,7 +388,8 @@ pub(crate) fn resolve_main_skill<'b>( pub fn resolve_main_skill_selection(build: &Build, data: &BuildData) -> Option<(usize, String)> { if let Some(n) = build.main_socket_group && let Some(group) = build.socket_groups.get(n.saturating_sub(1)) - && let Some((skill_id, level, set_index)) = pick_group_main_skill(data, group) + && let Some((skill_id, level, set_index)) = + pick_group_main_skill(data, group).or_else(|| pick_group_chosen_active(data, group)) && resolve_skill_level_with_gem_bonus(build, data, skill_id, level, set_index).is_some() { return Some((n.saturating_sub(1), skill_id.to_string())); diff --git a/crates/pobr-core/src/calc/offence.rs b/crates/pobr-core/src/calc/offence.rs index 46a92c82..c8d838cf 100644 --- a/crates/pobr-core/src/calc/offence.rs +++ b/crates/pobr-core/src/calc/offence.rs @@ -260,6 +260,17 @@ pub(crate) fn resolve_action_rate(db: &ModDb, cfg: &CalcConfig, input: &MinimalI input.base_action_rate * (1.0 + inc_speed / 100.0) * more_speed, ); let uncapped_action_rate = scaled_rate * action_speed_mod; + if std::env::var("POBR_DBG_SPEED").is_ok() { + eprintln!( + "[POBR_DBG_SPEED] base={} inc={} more={} action={} scaled={} names={:?}", + input.base_action_rate, + inc_speed, + more_speed, + action_speed_mod, + scaled_rate, + speed_names + ); + } round(apply_server_tick_cap( db, cfg, diff --git a/crates/pobr-core/src/calc/skill_use_time.rs b/crates/pobr-core/src/calc/skill_use_time.rs index ab7ac42b..0f9aab55 100644 --- a/crates/pobr-core/src/calc/skill_use_time.rs +++ b/crates/pobr-core/src/calc/skill_use_time.rs @@ -44,21 +44,23 @@ pub fn speed_names() -> [ModName; 3] { } /// 按技能类型选取速度 bucket:攻击 → `[AttackSpeed, SkillSpeed]`,法术 → `[CastSpeed, SkillSpeed]`, -/// 二者皆非(如纯 DoT/未标记)→ 取全部三个(向后兼容)。 +/// 二者皆非(如伙伴/召唤/预留类主技能)→ 仅 `[SkillSpeed]`。 /// /// 出处:PoB CalcOffence——攻击只吃攻速、法术只吃施法速度,`SkillSpeed` 两者通吃;不混淆 /// (避免攻击错误吃到 `increased Cast Speed`、法术错误吃到 `increased Attack Speed`)。 +/// 未标记技能:vendor 的 `Speed` mod 带 ModFlag.Attack/Cast,flag 匹配要求 cfg 含对应位 +/// ——非攻非法术 cfg 二者皆不吃(wolf-pack 实测:Wolf Pack 主技能曾错吃武器 +/// `12% reduced Attack Speed` → Speed 0.88 vs vendor 1.00)。 pub fn speed_names_for(cfg: &CalcConfig) -> Vec { // 攻击/法术判定同时认 ModFlags(orchestrator 经 `skill_type_flags` 注入)与 SkillTypes // (`CalcConfig::attack()`/`spell()` 预设),二者任一命中即可——兼容两条装配路径。 let is_attack = cfg.flags.intersects(ModFlags::ATTACK) || cfg.is_attack(); let is_spell = cfg.flags.intersects(ModFlags::SPELL) || cfg.is_spell(); - let untyped = !is_attack && !is_spell; let mut names = Vec::with_capacity(3); - if is_attack || untyped { + if is_attack { names.push(ModName::from(SPEED_BUCKET[0])); // AttackSpeed } - if is_spell || untyped { + if is_spell { names.push(ModName::from(SPEED_BUCKET[1])); // CastSpeed } names.push(ModName::from(SPEED_BUCKET[2])); // SkillSpeed(始终) From 77ed73570ae025fe41604d67195b29bf26251bc6 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 22:28:39 +0800 Subject: [PATCH 71/74] test(parity): raise baselines to full score (off 80/80, dot 37/37); record #15 Offensive 78->80 (gemling CritChance, wolf-pack Speed), dot 36->37 (gemling TotalDotDPS back in band), panel offensive ratchet 42/43 -> 45/47. def stays 450/450, core-8 144/144. All 18-build cells now hit at 5% - zero per-cell regressions by construction. Documents #15 root causes in docs/adapting-to-0.5.4b.md and corrects the stale 75a348e comment (its 6x+7 claim matched the pre-0.5.4b golden; current oracle/golden pin 1x+7 = 8.55). --- .../pobr-build/tests/parity/ninja_parity.rs | 42 ++++++++++++------ docs/adapting-to-0.5.4b.md | 44 +++++++++++++++++++ 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/crates/pobr-build/tests/parity/ninja_parity.rs b/crates/pobr-build/tests/parity/ninja_parity.rs index 1026fff3..9d709c3f 100644 --- a/crates/pobr-build/tests/parity/ninja_parity.rs +++ b/crates/pobr-build/tests/parity/ninja_parity.rs @@ -714,14 +714,11 @@ const BASELINE_DEF_HIT10: usize = 450; // #13+#14 合并实测 450/450 = 100%( // titan-shield-wall 连带。 // 三修复 build 互不重叠、增益叠加;合并后全量实测重记(master 62/70 → 70/73)。 // -// **radius-jewel × weapon-set 交互修复重记(gemling CritChance)**:非激活武器组专属点 -// 在 PoB2 仍留在 allocNodes(CalcSetup.lua:209-228),范围珠宝授予(`... in Radius also -// grant`,源=jewel)照样落到这些 notable 上。PoBR 原先把非激活组节点整个剔除 → gemling -// crit jewel 的 `7% increased Crit Hit Chance for Attacks` 只命中 1/6 个 in-radius -// notable(其余 5 个在 WeaponSet2)→ CritChance 8.55 vs 10.30(0.83x)。修复= -// `parse_passive_nodes` 单独回传被剔除的非激活组节点,`radius_jewel_expansions` 在几何里 -// 并回完整已分配集(节点自身 mod 仍 masking,行为不变)。gemling CritChance→1.00x、 -// AvgDamage/TotalDPS 0.96x→1.03x。其余 build 零回归(off 70/73 → 71/74)。 +// **radius-jewel × weapon-set 交互修复重记(gemling CritChance,⚠️已在 #15 回退)**: +// 曾把非激活武器组节点并回 radius 授予几何(当时 golden 10.30 = 6×+7)。0.5.4b 重采 +// golden 后 vendor 实测(oracle Tabulate)授予受**目标节点**的 `Condition:WeaponSet` +// 门控(CalcSetup.lua:222-223),非激活组授予净效果为零 → 正确值 8.55 = 1×+7, +// 机制整体回退(见 BASELINE_OFF_HIT5 上 #15 ①)。 // **Mageblood Diamond crit 名归一重记(+2 @5% 39→41 / +3 @10% 47→50)**:Mageblood // LegacyOfDiamond 注入 vendor 名 `CritChance` INC,但 calc::crit 读 `CriticalStrikeChance` // (PoBR 规范名)——裸注入不过 parser 的 translate_vendor_name,落死桶(同 Virtuous @@ -811,8 +808,25 @@ const BASELINE_DEF_HIT10: usize = 450; // #13+#14 合并实测 450/450 = 100%( // (CritChance/CritMult 1.00x;ES 12124→12437 vs golden 12434、MaxHit 五族 + // TotalEHP 0.97-0.98x→1.00x——def 列先前已在 5% 带内,def 计数不变)。 // 18 build 逐格 diff 仅此两 build 变动。 -const BASELINE_OFF_HIT5: usize = 78; // #10 进攻残差精修后 78/80(deadeye Point Blank DistanceRamp 0.83x→1.00x +2 格;twister Barrage repeats 0.98→1.00、warrior 局部 adds 泄漏 1.02/1.05→1.00 原在带内;#8 后 76;#6+#7 合并 73;迁移基线 39) -const BASELINE_OFF_HIT10: usize = 78; // #10 后 78/80(#8 后 76;#6+#7 合并 75;迁移基线 47;0.5.0=74) +// **#15 最后三格清零(off 78→80、dot 36→37,进攻/dot 满分)**: +// ① gemling CritChance 1.20x→1.00x:范围珠宝授予不再落到非激活武器组节点—— +// vendor 对 allocMode≠0 节点的**每条** mod(含珠宝授予)都加 `Condition:WeaponSet` +// (CalcSetup.lua:222-223,节点自身门控优先于珠宝来源分支 :224-227),非激活组 +// 授予净效果为零;oracle critModList 实证仅 1 条 `7 @ Tree:32763`(旧 75a348e 的 +// 「按 jewel allocMode 门控」判读有误,已整体回退)。同珠宝的 Small 授予 +// (Crossbow 伤害)同步收敛 → AvgDamage/TotalDPS 1.04x→0.97x(带内换向)。 +// ② gemling TotalDotDPS 1.10x→0.95x(转入带内):点燃链吃 crit/珠宝高估传导,修复后 +// 残差 -4.9%(952 vs 1001)分解 = 点燃几率 0.0962 vs 0.098636(0.9754)× 堆叠潜力 +// 0.2603 vs 0.26691(0.9752)——两者均 ∝ 击中火伤,0.9754² ≈ 0.951 与 dot 残差 +// 逐位吻合,即完全是 AvgDamage 0.97x 既有低估的下游(此前被 6× 珠宝授予掩盖)。 +// ③ wolf-pack Speed 1.43x→1.00x:主技能选择兜底——mainSocketGroup 指向的组无 +// 攻击/法术候选(Wolf Pack = Minion+Companion)时不再回退扫描其它组(曾错选 +// Blasphemy 组 Temporal Chains,0.7s 施放 → 1.43),改用组内 mainActiveSkill +// 选中项(vendor socketGroupSkillList 无伤害过滤);并修 speed bucket:非攻非 +// 法术技能不吃 Attack/CastSpeed INC(vendor ModFlag 匹配语义),Wolf Pack 不再 +// 错吃武器 `12% reduced Attack Speed`(0.88→1.00)。18 build 逐格零倒退(全命中)。 +const BASELINE_OFF_HIT5: usize = 80; // #15 满分 80/80(#10 后 78;#8 后 76;#6+#7 合并 73;迁移基线 39) +const BASELINE_OFF_HIT10: usize = 80; // #15 满分 80/80(#10 后 78;#8 后 76;迁移基线 47;0.5.0=74) /// DoT 三列(TotalDotDPS/WithDotDPS/CombinedDPS)独立基线(M4-G 扩列时实测; /// 新列单独常量,不动既有 BASELINE_OFF_*)。命中 3 = wolf-pack 双 0 命中 @@ -924,8 +938,8 @@ const BASELINE_OFF_HIT10: usize = 78; // #10 后 78/80(#8 后 76;#6+#7 合 // TotalDotDPS 1.06x→1.00x(182.40 vs 182.60)、monk frost-bomb 1.07x→0.99x // (4.53 vs 4.58);witch-lich 带内微降(21659→21621,仍 1.00x)。curse 槽位 // 归属逐 build 不变(druid 单槽仍 Temporal Chains 胜出,EW 依旧不入槽)。 -const BASELINE_DOT_HIT5: usize = 36; // #10+#11 合并实测 36/37(#10 单独 34:deadeye ignite 链随 Point Blank 修复 + smith/titan dot 回带;#11 单独 33:Blasphemy 半身修复 druid/frost-bomb;迁移基线 9;0.5.0=26) -const BASELINE_DOT_HIT10: usize = 36; // #10+#11 合并实测 36/37(#8+#9 合并 34;迁移基线 11;0.5.0=28) +const BASELINE_DOT_HIT5: usize = 37; // #15 满分 37/37(gemling TotalDotDPS 随 crit 修复入带;#10+#11 合并 36;迁移基线 9;0.5.0=26) +const BASELINE_DOT_HIT10: usize = 37; // #15 满分 37/37(#10+#11 合并 36;迁移基线 11;0.5.0=28) /// 面板口径(`mode_effective=false`)守卫基线:防止口径回归无感知(effective 与 /// panel 在防御侧逐值相同,故只守进攻)。M3-W5 切换 commit 实测。 @@ -943,8 +957,8 @@ const BASELINE_DOT_HIT10: usize = 36; // #10+#11 合并实测 36/37(#8+#9 合 /// (template.rs / special_mod.rs)同 commit 全量化——一批 `ModTag::SkillTypes` /// 域词条(Area/Projectile/Grenade 等)在 panel 口径开始正确匹配。effective /// 主口径与防御/进攻/dot 主基线逐值持平(纯 panel 侧收敛)。 -const PANEL_OFF_HIT5: usize = 42; // #6 与存量 #7 RemoveStats 各 +1 叠加实测 42(#4 后 40;Communion+Voices 38);迁移基线 27;0.5.0=44 -const PANEL_OFF_HIT10: usize = 43; // 同上叠加实测 43(#4 后 41;Communion+Voices 39);迁移基线 30;0.5.0=46 +const PANEL_OFF_HIT5: usize = 45; // #15 实测 45(gemling CritChance + wolf-pack Speed/DPS 面板同收);#6+#7 叠加 42;迁移基线 27;0.5.0=44 +const PANEL_OFF_HIT10: usize = 47; // #15 实测 47(同上);#6+#7 叠加 43;迁移基线 30;0.5.0=46 /// 回归门禁:聚合命中数不得低于已记录基线([`BASELINE_*`])。CI gate,防止改动倒退 parity。 #[test] diff --git a/docs/adapting-to-0.5.4b.md b/docs/adapting-to-0.5.4b.md index 3a238f16..23259760 100644 --- a/docs/adapting-to-0.5.4b.md +++ b/docs/adapting-to-0.5.4b.md @@ -599,6 +599,50 @@ reduced-requirements + armour-from-str build in corpus); alt quality stats consumed only by the reservation-efficiency path so far (accessor is general; offence-side alt stats wire up when a fixture demands). +**#15 — last three panel cells. ✅ DONE (off 78→80/80, dot 36→37/37 — +offensive and dot panels now full score alongside def 450/450).** + +1. **gemling CritChance 1.20x → 1.00x (10.30 → 8.55 exact).** Root cause: the + old 75a348e "keep inactive weapon-set nodes allocated for radius-jewel + grants" mechanism was based on a misreading — in vendor, *every* mod on an + `allocMode ≠ 0` node (the node's own stats **and** radius-jewel grants + alike) gets `Condition: WeaponSet` appended (CalcSetup.lua:222-223; the + jewel-source branch :224-227 only fires for allocMode-0 nodes), so grants + landing on inactive-set notables are dead. Oracle `critModList` Tabulate + pins exactly one `7 @ Tree:32763` (INC total 71 = 14+10+7+10+20+10 → + 5 × 1.71 = 8.55). The 75a348e-era claim of 6×+7 matched the *old* golden + (10.30); the 0.5.4b golden re-snap flipped it. Mechanism fully reverted + (build.rs field + xml_build plumbing + collect.rs geometry merge). The same + jewel's Small-node grants (crossbow damage) shrink too → gemling + AvgDamage/TotalDPS 1.04x → 0.97x (still in band, sign flipped). +2. **gemling TotalDotDPS 1.10x → 0.95x (in band).** After the crit fix the + residual decomposes exactly: ignite chance 0.0962 vs oracle 0.098636 + (0.9754) × stack potential 0.2603 vs 0.26691 (0.9752); both scale with the + hit's fire damage, and 0.9754² ≈ 0.951 matches the dot ratio bit-for-bit. + I.e. the dot residual is purely downstream of the pre-existing AvgDamage + 0.97x under-estimate (previously masked by the wrong 6× jewel grant); no + independent dot-side gap. +3. **wolf-pack Speed 1.43x → 1.00x.** Two stacked causes: ① main-skill + selection — Wolf Pack (`Minion`+`Companion`, neither Attack nor Spell) + failed PoBR's is-damage-skill filter, so the designated `mainSocketGroup=4` + yielded nothing and the fallback scan hijacked the main skill to the + Blasphemy group's Temporal Chains (0.7 s cast → Speed 1.43). Vendor's + `socketGroupSkillList` has **no** damage filter — fix: after + `pick_group_main_skill` misses on the *designated* group, fall back to the + `mainActiveSkill`-chosen active gem (`pick_group_chosen_active`; + cross-group scan still damage-only). ② speed bucket — with Wolf Pack as + main (castTime 1 s), PoBR's "untyped skill eats all three speed buckets" + fallback consumed the weapon's `12% reduced Attack Speed` → 0.88. Vendor + mod-matching requires the cfg to carry ModFlag.Attack/Cast for those mods + to apply; untyped skills eat neither → `speed_names_for` now returns only + `SkillSpeed` for non-attack/non-spell skills. Oracle mainOutput pins + Speed = 1, Time = 1, CastRate = 1 (statSetLabel "Minion Info", player + TotalDPS 0). + +18-build per-cell diff after #15: every offensive/dot/defensive cell is a +hit (450/450, 144/144, 80/80, 37/37) — zero regressions by construction. +Panel-mode offensive ratchet re-measured 42/43 → 45/47 (@5%/@10%). + ## Tooling - `examples/demo-bd-test/tools/recapture_golden.py` — refresh fixture goldens From ce4028ae344d3d6d0a9a488c9e3ad2ced5f8f4b4 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 22:50:39 +0800 Subject: [PATCH 72/74] test(wasm): stage overlay-common in memory backend; align guard tests with #7 memory_backend_matches_dir_backend only staged the version directory, so the P1-3 overlay-common layer was silently absent from the in-memory backend (production wasm bundles it via web sync-data). Stage it under the overlay-common/ key prefix the loader resolves. Two unsupported_specials tests still pinned the pre-#7 also_grants_guard phantom modeling; guard lines now assert loud unsupported reporting and no hit-pool extension, matching PoB2. The wasm suite was in no wave's merge gate, which is how both slipped through. --- apps/pobr-wasm/tests/contract_golden.rs | 35 ++++++++++++-------- apps/pobr-wasm/tests/unsupported_specials.rs | 27 ++++++++++----- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/apps/pobr-wasm/tests/contract_golden.rs b/apps/pobr-wasm/tests/contract_golden.rs index 802b4a9b..dd156dba 100644 --- a/apps/pobr-wasm/tests/contract_golden.rs +++ b/apps/pobr-wasm/tests/contract_golden.rs @@ -1135,21 +1135,30 @@ fn memory_backend_matches_dir_backend() { let request = serde_json::json!({ "pob_code": demo_code() }).to_string(); let from_dir = pobr_wasm::calculate_build_json(&request).expect("dir backend"); - // 把版本目录整体读入内存表,走 stage/init 路径重建。 + // 把版本目录整体读入内存表,走 stage/init 路径重建。版本无关策展层 + // `data/overlay-common/`(P1-3)位于版本目录的兄弟路径,内存后端按 + // `overlay-common/` 键解析(见 `pobr-gamedata::paths::overlay_common_path`), + // 生产 wasm 流程(web sync-data)同样打包该目录——staging 必须一并灌入, + // 否则内存后端静默丢失全部 curated special_mods。 let root = repo_data_root().join(pobr_data::GOLDEN_PARITY_DATA_VERSION); - for entry in walk_files(&root) { - // GameData 只读 JSON;跳过杂项文件(.DS_Store 等)。 - if entry.extension().and_then(|e| e.to_str()) != Some("json") { - continue; + let common_root = repo_data_root().join("overlay-common"); + let stage_tree = |tree_root: &std::path::Path, key_prefix: &str| { + for entry in walk_files(tree_root) { + // GameData 只读 JSON;跳过杂项文件(.DS_Store 等)。 + if entry.extension().and_then(|e| e.to_str()) != Some("json") { + continue; + } + let rel = entry + .strip_prefix(tree_root) + .unwrap() + .to_string_lossy() + .replace('\\', "/"); + let content = std::fs::read_to_string(&entry).expect("read data file"); + pobr_wasm::stage_data_file(&format!("{key_prefix}{rel}"), &content); } - let rel = entry - .strip_prefix(&root) - .unwrap() - .to_string_lossy() - .replace('\\', "/"); - let content = std::fs::read_to_string(&entry).expect("read data file"); - pobr_wasm::stage_data_file(&rel, &content); - } + }; + stage_tree(&root, ""); + stage_tree(&common_root, "overlay-common/"); pobr_wasm::init_staged_data().expect("init memory backend"); let from_memory = pobr_wasm::calculate_build_json(&request).expect("memory backend"); assert_eq!( diff --git a/apps/pobr-wasm/tests/unsupported_specials.rs b/apps/pobr-wasm/tests/unsupported_specials.rs index db2e36e4..b4cec050 100644 --- a/apps/pobr-wasm/tests/unsupported_specials.rs +++ b/apps/pobr-wasm/tests/unsupported_specials.rs @@ -1,6 +1,11 @@ //! 药剂/护符使用效果类词条经 special 表解析为正确机制语义,不再进未支持报表: -//! 免疫 → *Immune flag、Guard → GuardAbsorbRate/Limit(承受层真实吸收)、 -//! 瞬时回魔/魔力溢出/附身 → 结构化 mod 登记(vendor 对照与来源见 overlay source_note)。 +//! 免疫 → *Immune flag、瞬时回魔/魔力溢出/附身 → 结构化 mod 登记(vendor 对照 +//! 与来源见 overlay source_note)。 +//! +//! 例外:`Also grants N Guard` 曾被策展条目 also_grants_guard 建模为承受层吸收, +//! 但 vendor ModParser 根本不解析它——对 PoB2 golden 是幻影 Guard 池(存量 #7 +//! 摘除,ritualist EHP 1.10x→1.00x)。与 PoB2 对齐后它回到「未建模 → 响亮进 +//! unsupported 报表、不影响 hit pool」的口径。 use pobr_gamedata::repo_data_root; use serde_json::{Value, json}; @@ -53,27 +58,33 @@ fn charm_flask_use_effect_lines_are_recognized() { "Recover 295 Mana when Used", "Mana Recovery from Flasks can Overflow maximum Mana during Effect", "Possessed by Spirit Of The Stag for 19 seconds on use", - "Also grants 481 Guard", ] { assert!( !unsupported.iter().any(|u| u.contains(line)), "`{line}` 仍在未支持列表: {unsupported:?}" ); } + // guard 行未建模(vendor 同样不解析)——必须响亮上报而非静默丢弃。 + assert!( + unsupported + .iter() + .any(|u| u.contains("Also grants 481 Guard")), + "`Also grants 481 Guard` 应进未支持列表(#7 摘除幻影建模后与 PoB2 口径一致): {unsupported:?}" + ); } #[test] -fn guard_line_extends_hit_pool() { +fn guard_line_does_not_extend_hit_pool() { let dir = repo_data_root().join(pobr_gamedata::data_version()); pobr_wasm::init_data_from_dir(dir.to_str().unwrap()).expect("init data"); let without = calculate(false); let with = calculate(true); - // Guard 是生命/ES 前的全额吸收池(agent-docs/active-defences.md §2): - // 物理 max hit 应至少提高 Guard 池大小(481)。 + // #7 摘除 also_grants_guard 幻影建模后,guard 行不得再扩大 hit pool + // (vendor 不解析该行;此前的 +481 是 PoBR 单方面多建模的幻影池)。 let delta = stat(&with, "PhysicalMaxHit") - stat(&without, "PhysicalMaxHit"); assert!( - delta >= 481.0, - "Guard 481 应扩大物理 max hit(实际增量 {delta})" + delta.abs() < 1.0, + "guard 行不应改变物理 max hit(实际增量 {delta})" ); } From ba0b979aeb0f9dfa10a28b8a6fce57bbbde83682 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 23:04:53 +0800 Subject: [PATCH 73/74] fix(desktop): add corrupted field to example Item initializer (#13 fallout) --- apps/pobr-desktop/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/pobr-desktop/src/lib.rs b/apps/pobr-desktop/src/lib.rs index f8a3c08f..09a8574e 100644 --- a/apps/pobr-desktop/src/lib.rs +++ b/apps/pobr-desktop/src/lib.rs @@ -31,6 +31,7 @@ pub fn example_build() -> Build { base: ItemBaseId::from("Iron Ring"), rarity: ItemRarity::Rare, quality: 0, + corrupted: false, implicit_texts: vec![], modifier_texts: vec![ "+25 to maximum Life".to_string(), From 85d1b09feff4e21c15e3a8ef15bb99ef694a7633 Mon Sep 17 00:00:00 2001 From: ackness Date: Sat, 18 Jul 2026 23:12:50 +0800 Subject: [PATCH 74/74] chore(release): bump workspace version to 0.0.3 --- Cargo.lock | 30 +++++++++++++++--------------- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2cfea6e..9217c09c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "lint-i18n" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-i18n", ] @@ -393,7 +393,7 @@ dependencies = [ [[package]] name = "pobr-build" -version = "0.0.2" +version = "0.0.3" dependencies = [ "base64", "criterion", @@ -410,7 +410,7 @@ dependencies = [ [[package]] name = "pobr-cli" -version = "0.0.2" +version = "0.0.3" dependencies = [ "clap", "pobr-build", @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "pobr-core" -version = "0.0.2" +version = "0.0.3" dependencies = [ "aho-corasick", "criterion", @@ -435,7 +435,7 @@ dependencies = [ [[package]] name = "pobr-data" -version = "0.0.2" +version = "0.0.3" dependencies = [ "serde", "serde_json", @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "pobr-data-adapter" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-data", "serde", @@ -452,7 +452,7 @@ dependencies = [ [[package]] name = "pobr-desktop" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-build", "pobr-core", @@ -462,7 +462,7 @@ dependencies = [ [[package]] name = "pobr-gamedata" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-core", "pobr-data", @@ -473,7 +473,7 @@ dependencies = [ [[package]] name = "pobr-i18n" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-data", "serde", @@ -483,7 +483,7 @@ dependencies = [ [[package]] name = "pobr-item" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-core", "pobr-data", @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "pobr-trade" -version = "0.0.2" +version = "0.0.3" dependencies = [ "serde", "serde_json", @@ -500,7 +500,7 @@ dependencies = [ [[package]] name = "pobr-tree" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-data", "serde", @@ -510,7 +510,7 @@ dependencies = [ [[package]] name = "pobr-wasm" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-build", "pobr-core", @@ -525,7 +525,7 @@ dependencies = [ [[package]] name = "precompile-mods" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-core", "pobr-data", @@ -699,7 +699,7 @@ dependencies = [ [[package]] name = "sync-pob-catalog" -version = "0.0.2" +version = "0.0.3" dependencies = [ "pobr-core", "pobr-data", diff --git a/Cargo.toml b/Cargo.toml index b073a0a4..ee432da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.0.2" +version = "0.0.3" edition = "2024" [workspace.dependencies]