Skip to content

Issue #31: radius-jewel framework#191

Merged
jonatanferm merged 1 commit into
mainfrom
claude/issue-31-radius-jewel-framework
May 9, 2026
Merged

Issue #31: radius-jewel framework#191
jonatanferm merged 1 commit into
mainfrom
claude/issue-31-radius-jewel-framework

Conversation

@jonatanferm
Copy link
Copy Markdown
Owner

Summary

Generic mechanic for "a jewel socketed in a tree socket modifies passive nodes inside its radius" — the foundation that Timeless jewels (#30) and Cluster jewels (#21) both build on. This PR ships the generic infrastructure plus end-to-end identification + application of vanilla node-modifying radius jewels (e.g. 10% increased Maximum Life to nearby allocated passives on a Crimson Jewel).

  • pob_data::jewel_radius: canonical 3.16+ radii table (Small=960, Medium=1440, Large=1800, Very Large=2400, Massive=2880) plus the 3.15-and-older fallback, with a tree-version picker that mirrors PoB's setJewelRadiiGlobally.
  • pob_engine::jewel_radius: node-position math (group + orbit + orbit_index → Cartesian), nodes_in_radius / allocated_nodes_in_radius for scanning, identify_radius_jewel for detecting vanilla radius jewels via mod text, and apply_radius_jewels for the per-allocated-node mod replay. The radius-suffix stripper ( to nearby allocated passives, from Passives in Radius, …) lets mod_parser mint canonical names (Life, Strength) instead of long-form aliases.
  • Character::socketed_jewels (SocketedJewels keyed by tree-socket node id) round-trips through CharacterSnapshot. perform::init_env_with_bases calls apply_radius_jewels after item application; each in-radius allocated node receives one mod copy sourced as Source::Passive(target_node) so the breakdown attributes the bonus to the in-radius node.
  • Cluster / Abyss / Timeless / Charm jewels share the same storage but are filtered out by identify_radius_jewel's subtype check — they keep their own dispatch paths in Cluster jewel sub-graph support #21 / Timeless jewels — keystone/notable replacement within radius #30.

Deferred to follow-ups (tracked by their own issues):

Test plan

  • cargo fmt --all -- --check clean.
  • cargo clippy --workspace --all-targets -- -D warnings clean.
  • cargo test --workspace — 302 tests pass (was 291 baseline).
  • 11 unit tests in pob_data::jewel_radius + pob_engine::jewel_radius cover the radii table, position math, scan, identification (Cluster/Abyss/Timeless skipped, non-radius rare jewel skipped, explicit ring sizes), suffix stripping, and end-to-end mod emission.
  • 7 integration tests in crates/pob-engine/tests/radius_jewels.rs exercise the framework against the real 3_25.json tree fixture, including a regression that empty-alloc compute is byte-identical to baseline (no drift when no nodes are in radius).
  • End-to-end test verifies socketing 10% increased Maximum Life to nearby allocated passives into a real tree socket plants an Inc Life mod sourced from the in-radius allocated passive in the modDB.

🤖 Generated with Claude Code

Generic mechanic for "a jewel socketed in a tree socket modifies passive
nodes inside its radius". Ships node-position math, in-radius scans,
vanilla node-modifying-jewel identification, and a per-allocated-node
mod replay pass — the foundation Timeless (#30) and Cluster (#21) follow-ups
plug into.

* pob_data::jewel_radius: canonical 3.16+ radii table (Small=960,
  Medium=1440, Large=1800, Very Large=2400, Massive=2880) plus the
  3.15-and-older fallback, with a tree-version picker.
* pob_engine::jewel_radius: node_position / nodes_in_radius /
  allocated_nodes_in_radius / identify_radius_jewel / apply_radius_jewels.
  Strips "to nearby allocated passives" / "from Passives in Radius"
  trailers before parsing so mod_parser mints canonical names
  (Life, Strength, ...) instead of long-form aliases.
* Character::socketed_jewels stores SocketedJewels keyed by tree-socket
  NodeId; round-trips through CharacterSnapshot.
* perform::init_env_with_bases runs apply_radius_jewels after item
  application, emitting one mod copy per in-radius allocated node
  sourced as Source::Passive(target_node).
* Cluster / Abyss / Timeless / Charm jewels share the storage but
  are filtered out by identify_radius_jewel's subtype check; they
  route through their own dispatch paths in #21 / #30.

Tests:
* 11 unit tests in pob_data::jewel_radius and pob_engine::jewel_radius
  cover the radii table, position math, in-radius scans, identification
  heuristics, suffix stripping, and end-to-end mod emission.
* 7 integration tests in radius_jewels.rs exercise the framework
  against the real 3_25.json tree fixture, including a regression
  asserting empty-alloc compute is unchanged from baseline.

cargo fmt --all --check, cargo clippy --workspace --all-targets
-- -D warnings, and cargo test --workspace are all clean
(302 tests pass, up from 291 baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@jonatanferm jonatanferm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[loop-status: lgtm] Foundation for the radius-jewel mechanic — Timeless (#30) and the named-unique handlers (Watcher's Eye etc.) build on this. Right architecture: pob_data::jewel_radius ships the canonical radii table with tree-version picker, pob_engine::jewel_radius exposes the math + scan + identification + replay; Character.socketed_jewels round-trips through CharacterSnapshot. Each in-radius allocated node receives a Source::Passive(target_node) attribution so breakdown shows the right contributing chain. HandlerKind enum is the right dispatch surface for unique-named handlers landing later. Conflict with #190 was a clean dual-storage situation (cluster's Character.jewels alongside radius's Character.socketed_jewels) — kept both since they target different mechanics; the PR body itself flags that the two will eventually unify. CI green post-rebase, +18 tests including end-to-end against the live 3.25 tree with a regression for empty-alloc baseline drift.

@jonatanferm jonatanferm merged commit 8910cc1 into main May 9, 2026
4 checks passed
@jonatanferm jonatanferm deleted the claude/issue-31-radius-jewel-framework branch May 9, 2026 08:34
jonatanferm added a commit that referenced this pull request May 9, 2026
…hy Mind / Fertile Mind) (#226)

Plug three named uniques into the framework PR #191 set up — each gets a
bespoke `HandlerKind` variant and dedicated dispatch in
`apply_radius_jewels`. Watcher's Eye exercises the aura-conditional path;
the two transformer uniques exercise per-attribute mod transforms.

- `WatchersEye`: bypasses the radius scan; mods land globally with their
  per-aura `AffectedBy<Aura>` Condition tag (emitted by `mod_parser`'s new
  dynamic `match_while_var_dyn` for "while affected by <Aura>" clauses).
  A new `detect_active_auras` pass in perform.rs flips on the matching
  conditions for every enabled aura/herald gem on the player.
- `LifeToManaTransform` (Healthy Mind): walks each in-radius allocated
  node's `.stats`, finds Inc Life mods, emits Inc Mana mods at 200% scale
  sourced as the in-radius node so per-node breakdowns line up.
- `DexToIntTransform` (Fertile Mind): per-attribute Base mod transform
  with a counter `-N Dex` so the source attribute is moved, not duplicated.

Intuitive Leap and Pure Talent are deferred to follow-up slices —
Intuitive Leap needs pathfind connectivity changes; Pure Talent needs
class-conditional notable-buff machinery.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants