Skip to content

feat(dr): opt-in cambrinth charge distribution across configured items - #1549

Open
simtel12 wants to merge 1 commit into
elanthia-online:mainfrom
simtel12:feat/drca-cambrinth-distribution
Open

feat(dr): opt-in cambrinth charge distribution across configured items#1549
simtel12 wants to merge 1 commit into
elanthia-online:mainfrom
simtel12:feat/drca-cambrinth-distribution

Conversation

@simtel12

@simtel12 simtel12 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Problem

DRCA charges cambrinth incorrectly when cambrinth_items lists more than one item. There are two separate faults.

charge_cambrinth_items repeats a flat list on every item. A spell configured as cambrinth: [4, 10] reaches charge_cambrinth_items as a flat Array of Integer. The when Integer branch passes the whole list to every item, so a 14 mana plan charges 14 into the first item and 14 more into the second.

calculate_mana ignores each item's cap. The use_auto_mana path uses cap only as a ratio to pick a charge count, then gives every charge of an item the same size. Nothing checks that an item's total fits its cap, and the remainder of an integer division is dropped. With caps 4/4/48 and 3 charges it produces [[7], [7], [7, 7, 7]] — 7 mana into a cap-4 item, and 1 mana lost.

cast.lic is unaffected, because it calculates the split itself before it calls DRCA.

Change

A new per-character setting, cambrinth_distribute_charges, selects a second code path. It is unset by default.

With the setting on, allocate_cambrinth_charges gives each item as much as its cap allows, in the order the profile lists them, so a small worn item fills before a large stored one. Each item then splits its own share into charges. Spare charges go to the largest charge while that makes it smaller, which keeps each charge inside what the character can channel at once. Mana that fits in no item returns to the base prep.

caps 4/4/48, cambrinth_num_charges: 3, discern minimum 20

  to charge   before                   charged   after              charged
  13          [[2], [2], [2, 2, 2]]    10        [[4], [4], [5]]    13
  20          [[4], [4], [4, 4, 4]]    20        [[4], [4], [12]]   20
  36          [[7], [7], [7, 7, 7]]    35        [[4], [4], [28]]   36

In the first and third rows the old code drops mana. In the third it also
asks a cap-4 item to hold 7.

A flat charge list written in a profile is placed one charge at a time into the first item with room, rather than skipping an item for good once a charge has passed it by.

Cached discern data records the cambrinth caps it was calculated against, so a change to cambrinth_items invalidates the cache. Without that, a distribution built for the old item list is replayed for up to check_discern_timer_in_hours, which charges one item and silently skips the rest.

A single cambrinth item is left alone

Even with the setting on, one item takes its charge list unchanged. In that path cambrinth_cap feeds the arcana check in skilled_to_charge_while_worn? rather than a charge limit, and many profiles charge well past it on purpose. Enforcing it there would quietly reduce charging for a large number of existing setups.

Evidence that the default path is unchanged

I ran the patched module with the setting off against a verbatim copy of main's algorithms and made any difference raise.

  • charge_cambrinth_items: all 1638 spell entries carrying a cambrinth list, across the 270 profiles in dr-scripts/profiles and my own runtime profile directory. No divergence.
  • calculate_mana: 55 distinct cambrinth configurations over 180 discern results each. No divergence.

With the setting on, 13 of those 1638 entries change. All 13 belong to the three profiles that configure several items, including Samples/WarriorMage/Dartellum-setup.yaml, which today charges 240 mana for a 60 mana plan.

One behaviour-preserving cleanup

calculate_mana built its charge array with arr[i] += 1 on a nil slot, which works only because Lich patches NilClass#+. It is now written as (arr[i] || 0) + 1. Identical result, and the default path is testable without the global patch — which is why it had no coverage before.

Testing

  • bundle exec rspec — 6383 examples, 0 failures. 60 new examples in spec/lib/dragonrealms/commons/common_arcana_spec.rb, five of which pin the default path so a later change cannot quietly flip the gate.
  • A property test sweeps three cambrinth configurations against every mana value from 0 to 120 and charge budgets 1 to 6, asserting that mana is conserved, no cap is exceeded, the charge budget holds, and no zero-sized charge is produced.
  • bundle exec rubocop — clean.

Companion change

elanthia-online/dr-scripts documents the new setting in profiles/base.yaml and teaches cast.lic to call DRCA.allocate_cambrinth_charges, so ,cast and the waggle scripts share one implementation. That PR checks the method exists and falls back to its own split on an older Lich, so it does not depend on this one landing first.

Test plan

  • Existing profiles with one cambrinth item behave exactly as before, with and without the setting.
  • A profile with several cambrinth_items and a flat cambrinth: list charges each item its own share instead of the full list.
  • use_auto_mana with several items never charges an item past its cap.
  • Changing cambrinth_items causes the next cast to discern again rather than reuse the old distribution.

DRCA charges cambrinth wrong when cambrinth_items holds more than one item.
This adds the fix behind a new per-character setting,
cambrinth_distribute_charges, and leaves the default path unchanged.

charge_cambrinth_items passes a flat charge list from YAML (cambrinth:
[4, 10]) to every item, so each item is charged the full amount. A 14 mana
plan charges 14 mana into the ring and 14 more into the armband.

calculate_mana, the use_auto_mana path, uses each item's cap only as a
ratio to pick a charge count, then gives every charge the same size. It
charges past the cap of the small items and drops the remainder of an
integer division, so mana goes nowhere. With caps 4/4/48 and 3 charges it
produces [[7], [7], [7, 7, 7]]: 7 mana into a 4 cap item, and 1 mana lost.

With cambrinth_distribute_charges set, allocate_cambrinth_charges gives
each item as much as its cap allows, in the order the profile lists them,
so a small worn item fills before a large stored one. Each item then splits
its own share into charges. Spare charges go to the largest charge while
that makes it smaller, which keeps each charge inside what the character
can channel at once. Mana that fits in no item returns to the base prep.

  caps 4/4/48, 3 charges
    13 mana to charge -> [[4], [4], [5]]
    20 mana to charge -> [[4], [4], [12]]
    36 mana to charge -> [[4], [4], [28]]

A flat charge list written in a profile is placed one charge at a time into
the first item with room, rather than skipping an item for good once a
charge has passed it by.

A single cambrinth item keeps the old behaviour even with the setting on.
In that path cambrinth_cap feeds the arcana check in
skilled_to_charge_while_worn? rather than a charge limit, and many profiles
charge well past it on purpose.

Cached discern data records the cambrinth caps it was calculated against,
so a change to cambrinth_items invalidates the cache instead of replaying a
distribution built for the old item list.

The setting is unset by default, so every existing profile keeps its
current behaviour. I checked that against all 270 profiles in dr-scripts
and my runtime scripts directory: with the setting off, both paths produce
byte-identical results to the current code for all 1638 spell entries that
carry a cambrinth list, and for calculate_mana across 55 distinct cambrinth
configs over 180 discern results each. With it on, 13 entries change, all
in the three profiles that configure several items.

calculate_mana no longer depends on the global NilClass#+ patch to build
its charge array. The behaviour is the same, and the default path is now
testable.
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 8614e722-511e-4af8-a64a-357af5aeb6ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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