Skip to content

feat: initial PoE2 0.5 (Return of the Ancients) passive tree support#1977

Closed
0xCZR1 wants to merge 1 commit into
PathOfBuildingCommunity:devfrom
0xCZR1:dev
Closed

feat: initial PoE2 0.5 (Return of the Ancients) passive tree support#1977
0xCZR1 wants to merge 1 commit into
PathOfBuildingCommunity:devfrom
0xCZR1:dev

Conversation

@0xCZR1

@0xCZR1 0xCZR1 commented May 26, 2026

Copy link
Copy Markdown

feat: PoE2 0.5 (Return of the Ancients) passive tree support

Summary

Adds initial support for the PoE2 0.5 passive tree. The tree loads, renders, and is fully browsable with clean tooltips, working search, real node icons, and attribute-node (STR/DEX/INT) switching. GGG made large breaking changes to the tree JSON schema between 0.4 and 0.5; rather than rewrite PoB's Lua parser, a Python converter translates 0.5 data.json into a 0.4-compatible shape, plus a handful of defensive Lua patches. The existing calc engine already handles the majority of 0.5 nodes because the stat-mod text is largely unchanged — only the JSON structure changed. New 0.5 mechanics with no formula support are honestly shown as "Not supported" rather than silently ignored.

Screenshot 2026-05-26 155354

Schema changes (0.4 → 0.5)

Top-level

0.4 0.5 Converter action
Removed assets, connectionArt, constants, ddsCoords, nodeOverlay Borrowed from 0.4 reference tree.json
Added edges, skillOverrides Connections derived from node out/in

Classes

0.4 0.5 Converter action
integerId (removed) Synthesized from array index
background (obj) image, image_offset_x/y Omitted (no class background renders)
8 classes 12 classes (+ Marauder, Duelist, Shadow, Templar) All included; new 4 have no ascendancies yet

Ascendancies

0.4 0.5 Converter action
id = name ("Deadeye") id = class-indexed ("Ranger1") id set to name; 0.5 id → internalId
internalId (removed) See above
background image, offsetX/Y, flavourText* Not converted

Nodes

0.4 0.5 Converter action
ascendancyName ascendancyId Mapped via class data ("Ranger1" → "Deadeye")
classesStart (names) classStartIndex (indices) Indices → names
connections [{id,orbit}] out/in (id arrays) out → connections
isAttribute + options isGenericAttribute Synthesized: isAttribute + 3-option STR/DEX/INT list (see below)
isFreeAllocate isFree Mapped
containJewelSocket isJewelSocket Mapped
isOnlyImage isMastery Mapped
(N/A) grantedSkill (gem object) Extracted into stats for tooltip display
(N/A) flavourText (can be table) Converted to string + markup stripped

Groups

  • JSON array → JSON object (dict, string keys): converted dict→list preserving order.
  • Node IDs intstring: converted back to ints.

Stat-text markup (new in 0.5)

  • [InternalTag|Display Text]Display Text; [Tag]Tag
  • <tag>{content}content; stray <tag>/</tag> stripped

Architecture

  • src/Export/convert_05_tree.py is a permanent schema translation layer, not a temporary hack — it documents every field mapping.
  • The existing calc engine handles the bulk of 0.5 nodes correctly because stat-mod text is unchanged; only the JSON structure differs.
  • Adding support for new 0.5 keywords is the same incremental workflow as any league update — no parser rewrite needed.

What's implemented

  • Tree loading / rendering / browsing on full 0.5 data (4,863 nodes)
  • Node icons extracted from GGG's webp spritesheets (src/Export/extract_05_icons.py) — 517 of 568 (the 51 misses are Mastery* icons not present in the skill atlases)
  • Attribute-node switching (STR/DEX/INT, +5 each) — synthesized isAttribute + options (matches the GGG export script: isAttribute only, not isSwitchable)
  • Search (with unlockConstraint nil guard for 0.5 node-based constraints)
  • Clean tooltips (stat-markup cleanup) and granted-skill descriptions visible in tooltips
  • Keystone calc support: 21 of 33 keystones fully parse (Avatar of Fire, Blood Magic, Chaos Inoculation, Iron Reflexes, Resolute Technique, Pain Attunement, Mind Over Matter, Eldritch Battery, Vaal Pact, Zealot's Oath, Unwavering Stance, Glancing Blows, Eternal Youth, Whispers of Doom, Blackflame Covenant, Bulwark, Crimson Assault, Giant's Blood, Lord of the Wilds, Necromantic Talisman, Oasis)
  • New ModParser patterns added (each maps to an existing, calc-consumed mod/flag):
    • cannot recover energy shield from regenerationNoEnergyShieldRegen (Scarred Faith)
    • you cannot recover energy shield to above armourArmourESRecoveryCap (Scarred Faith)
    • mana costs are doubledManaCost MORE 100 (Eldritch Battery)
    • convert (n)% of maximum energy shield to maximum manaEnergyShieldConvertToMana (Eldritch Battery)
    • leech life (n)% less quicklyLifeLeechRate MORE -n (Vaal Pact)
    • totems gain +(n)% to all maximum elemental resistancesTotemElementalResistMax BASE n (totem notable cluster; properly totem-scoped, not global)
  • ModCache regenerated so the new patterns activate
scarred_faith

What's NOT implemented

These fall into a few distinct categories — most are not "0.5 calc work to be done," and the "Not supported" label is the honest/correct state for them:

  • New 0.5 mechanics with no formula (would need new calc logic to ever support): Archon, Glory, Infusion Remnants, Shapeshift, Companions, Djinns, Concoctions, Bells, Invocations, Puppet Master.
  • Not supported by PoB by nature — not 0.5-specific. Many lines describe behaviour the calc engine never models regardless of version: flavour/utility text, on-kill/on-hit narrative effects, summon/entity descriptions, etc. These show "Not supported" in 0.4 too; they aren't a 0.5 regression and aren't expected to be calculated.
  • Granted-skill description text the converter injects into tooltips for readability (e.g. "Minions from this skill have 1% increased Damage" — a per-level skill descriptor, not a passive mod). Recognised as informational, not a calc gap.
  • Scoped stat lines whose base stat parses but whose scope is a new mechanic/skill-type with no existing tag (e.g. "Echoed Spells have…", "Invocation Spells have…", "Offerings have…", "Remnants you create have…", "Empowered Skills have…"). Faking these by ignoring the scope would apply bonuses globally and inflate stats — left unsupported on purpose. Where the scope does already exist (e.g. totem max-resistances) it was implemented with the proper tag.
  • Full DPS for new ascendancy granted skills (descriptions shown as tooltip text only).
  • Class/ascendancy backgrounds (0.5 changed the format); ascendancies for the 4 new classes (unrevealed, nodes stripped).

Parse catalog (verified via headless harness)

  • Total unique node stat strings: 3,038
  • Parsing OK: 2,095 · Partial: 279 · Unknown: 664
  • The unknowns are not all "0.5 work to do." They break down as: a small set of scoped stats with no existing scope tag (unsafe to fake); a large set that share vocabulary with ModParser but are descriptive/flavour or otherwise unsupported by PoB by nature (these read as "Not supported" in 0.4 too — not a 0.5 gap); converter-injected granted-skill descriptions; and a genuinely-new-mechanic remainder.
  • A headless triage (clear in-memory ModCache → parse fresh → try reworded variants → search ModParser for scope words) classified every unknown, so the "implementable" subset is bounded. Only stats whose mod/flag and scope already exist in PoB (e.g. NoEnergyShieldRegen, TotemElementalResistMax) were implemented; the rest are left as honest "Not supported" rather than faked.

Setup

git clone https://github.com/grindinggear/poe2-skilltree-export.git /tmp/poe2-tree
python3 src/Export/convert_05_tree.py /tmp/poe2-tree/data.json src/TreeData/0_4/tree.json src/TreeData/0_5/data.json
python3 src/Export/extract_05_icons.py /tmp/poe2-tree/assets src/TreeData/0_5/data.json src/TreeData/0_5
# copy 0.4 orbit/background sprites into 0_5/ as placeholders if not present
# Launch PoB — it auto-generates tree.lua from data.json

Notes for maintainer

  • ModCache was regenerated on Windows using a lua-utf8 stub (only used for number formatting, so parsing is unaffected). The check_modcache CI job should be re-run in the canonical environment to produce the authoritative cache.
  • The headless verification harness (luajit + module stubs + CI=1/REGENERATE_MOD_CACHE=1, with an in-memory ModCache clear to parse fresh) is the recommended workflow for adding further 0.5 mod patterns.
  • parse_fail.txt / the unknown-stat triage are available as references for prioritizing remaining mod work.

Tree stats (0.5)

  • 12 classes (4 new, no ascendancies yet) · 24 revealed ascendancies (+12 unrevealed)
  • 4,863 nodes (after stripping unrevealed ascendancy placeholders)
  • 33 keystones · 359 mastery nodes · 568 unique node icons

- Schema converter (0.5 data.json -> 0.4-compatible format)
- Icon extraction from GGG webp spritesheets
- Attribute node switching (STR/DEX/INT)
- Keystone calc support where formulas exist
- Mod parser patterns for new 0.5 stat text (ES regen/cap, EB ES->mana
  & mana cost, Vaal Pact leech rate, totem max elemental resistances)
- ModCache regenerated
- Lua patches: 12-class support, nil guards, pcall fallback, asset found flag
- Stat text markup cleanup ([Tag|Display] stripping)
- Full schema diff documented in PR description

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@0xCZR1 0xCZR1 closed this May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant