From 1cfdc256450f7446a91d6e8f5bc6c577d6b4101d Mon Sep 17 00:00:00 2001 From: maven Date: Sun, 26 Jul 2026 21:00:01 -1000 Subject: [PATCH 1/3] docs: add design spec for Cane of Kulemak Catarina mod support Co-Authored-By: Claude Fable 5 --- ...26-cane-of-kulemak-catarina-mods-design.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md diff --git a/docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md b/docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md new file mode 100644 index 00000000000..d89b9d38777 --- /dev/null +++ b/docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md @@ -0,0 +1,76 @@ +# Cane of Kulemak: restore missing Catarina signature mods + +**Date:** 2026-07-26 +**Status:** Approved + +## Problem + +Cane of Kulemak is a programmatically generated unique (`src/Data/Uniques/Special/Generated.lua`). +Its variant dropdowns are built by filtering `data.veiledMods` (`src/Data/ModVeiled.lua`) through +`veiledModIsActive`, which only accepts a mod if `weapon`, `staff`, or `two_hand_weapon` appears in +the mod's `weightKey` with a positive weight. + +Catarina's signature veiled mods do not use those keys. They carry their spawn weight on +`catarina_veiled_prefix` (weight 2000) and list only the slots they are *excluded* from (weight 0). +A staff is not in their exclusion lists, so in game they can roll on Cane of Kulemak, but PoB's +filter drops them. Five weapon-legal variants are missing from the item: + +1. `JunMasterVeiledLocalIncreaseSocketedSupportGemLevel` — `+2 to Level of Socketed Support Gems` + / `+(5-8)% to Quality of Socketed Support Gems` (single mod, two lines — the motivating case) +2. `JunMasterVeiledChaosExplosionOnKill` — enemies killed have (15-25)% chance to explode +3. `JunMasterVeiledSupportedByCastOnCritAndSpellDamage` +4. `JunMasterVeiledSupportedByCastWhileChannelingAndSpellDamage` +5. `JunMasterVeiledSupportedByArcaneSurgeAndSpellDamage` + +The other ~23 Catarina mods must remain excluded: they explicitly zero the `weapon` key. + +## Scope + +Cane of Kulemak only. Paradoxica and Replica Paradoxica generation must be byte-for-byte +unchanged (Replica Paradoxica's analogous gap with master signature mods is a possible follow-up, +out of scope here). + +## Design + +### Filter change (`src/Data/Uniques/Special/Generated.lua`) + +- Add an optional `poolKey` parameter to `veiledModIsActive(mod, baseType, specificType1, + specificType2, poolKey)`. +- New rule, appended to the existing checks: if **none** of `baseType`/`specificType1`/ + `specificType2` appear in the mod's `weightKey`, but `poolKey` does with weight > 0, the mod is + active. +- Explicit slot keys keep priority: a mod listing `weapon` with weight 0 stays inactive regardless + of pool key (this is what keeps the minion/spectre/flask Catarina mods off the staff, and it + mirrors the game's first-matching-weightKey-wins semantics). +- `getVeiledMods` passes `"catarina_veiled_prefix"` as `poolKey` only when + `veiledPool == "catarina"`. A nil `poolKey` preserves old behavior exactly. + +### Knock-on effects + +- The five new variants are inserted into the alphabetically sorted variant list, shifting the + indices of later variants. The hardcoded `Selected Variant` / `Selected Alt Variant` defaults + (`Generated.lua` lines 114–117) must be re-pointed so the same default mods stay selected. + Verify by generating the item text before and after. +- No new mod data is needed. Both target lines are already flagged scalable in + `src/Data/ModScalability.lua`, so the staff's `(50-70)% increased Unveiled Modifier magnitudes` + implicit scales them automatically via existing machinery. + +### Testing + +- New busted spec (in `spec/System/`, following existing `Test*_spec.lua` conventions) asserting: + - the generated Cane of Kulemak item text contains `+2 to Level of Socketed Support Gems` and + `+(5-8)% to Quality of Socketed Support Gems`; + - a weapon-illegal Catarina mod (e.g. `+1 to maximum number of Spectres`) is still absent; + - Paradoxica and Replica Paradoxica generated text is unchanged (no Catarina signature mods + leak in). +- Run the full spec suite to confirm no regressions. + +### Error handling + +None needed — pure data generation at program load; no user input or runtime failure paths. + +## Success criteria + +Selecting Cane of Kulemak from the unique list in PoB offers +"(Prefix) Increase Socketed Support Gem Level" (support gem level + quality) as a selectable +variant in the variant dropdowns, with values that scale with the unveiled-magnitude implicit. From 28858016447d8abcab8ae91441e1542348c6b908 Mon Sep 17 00:00:00 2001 From: maven Date: Sun, 26 Jul 2026 21:16:37 -1000 Subject: [PATCH 2/3] fix: add missing Catarina veiled mods to Cane of Kulemak variants The generated Cane of Kulemak only accepted veiled mods whose weightKey lists weapon/staff/two_hand_weapon with positive weight. Catarina's signature mods instead carry their weight on catarina_veiled_prefix and only list excluded slots, so five weapon-legal variants were missing, notably +2 to Level of Socketed Support Gems / +(5-8)% to Quality of Socketed Support Gems. Teach the filter to fall back to the pool weight key when no slot key matches, and re-point the default selected variants at the same mods as before. Co-Authored-By: Claude Fable 5 --- spec/System/TestGeneratedUniques_spec.lua | 33 +++++++++++++++++++++++ src/Data/Uniques/Special/Generated.lua | 15 +++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 spec/System/TestGeneratedUniques_spec.lua diff --git a/spec/System/TestGeneratedUniques_spec.lua b/spec/System/TestGeneratedUniques_spec.lua new file mode 100644 index 00000000000..a68f5935bbc --- /dev/null +++ b/spec/System/TestGeneratedUniques_spec.lua @@ -0,0 +1,33 @@ +describe("Generated uniques", function() + local function findGeneratedItem(name) + for _, itemText in ipairs(data.uniques.generated) do + if itemText:sub(1, #name + 1) == name .. "\n" then + return itemText + end + end + end + + it("includes weapon-legal Catarina veiled mods on Cane of Kulemak", function() + local cane = findGeneratedItem("Cane of Kulemak") + assert.is_not_nil(cane) + assert.is_not_nil(cane:find("+2 to Level of Socketed Support Gems", 1, true)) + assert.is_not_nil(cane:find("+(5-8)% to Quality of Socketed Support Gems", 1, true)) + assert.is_not_nil(cane:find("chance to Explode", 1, true)) + end) + + it("excludes weapon-illegal Catarina veiled mods from Cane of Kulemak", function() + local cane = findGeneratedItem("Cane of Kulemak") + assert.is_not_nil(cane) + assert.is_nil(cane:find("maximum number of Spectres", 1, true)) + assert.is_nil(cane:find("Minions are Aggressive", 1, true)) + end) + + it("does not add Catarina signature mods to Paradoxica or Replica Paradoxica", function() + for _, name in ipairs({ "Paradoxica", "Replica Paradoxica" }) do + local item = findGeneratedItem(name) + assert.is_not_nil(item) + assert.is_nil(item:find("to Quality of Socketed Support Gems", 1, true)) + assert.is_nil(item:find("chance to Explode", 1, true)) + end + end) +end) diff --git a/src/Data/Uniques/Special/Generated.lua b/src/Data/Uniques/Special/Generated.lua index 72fb68e188e..007222a3abc 100644 --- a/src/Data/Uniques/Special/Generated.lua +++ b/src/Data/Uniques/Special/Generated.lua @@ -18,17 +18,22 @@ local parseVeiledModName = function(string) gsub("(%d)", " %1 ")) end -local veiledModIsActive = function(mod, baseType, specificType1, specificType2) +local veiledModIsActive = function(mod, baseType, specificType1, specificType2, poolKey) local baseIndex = isValueInTable(mod.weightKey, baseType) local typeIndex1 = isValueInTable(mod.weightKey, specificType1) local typeIndex2 = isValueInTable(mod.weightKey, specificType2) + -- Master signature mods (e.g. Catarina's) carry their weight on a pool key such as "catarina_veiled_prefix" + -- and only list the slots they are excluded from, so the pool key only applies when no slot key matched + local poolIndex = poolKey and isValueInTable(mod.weightKey, poolKey) return (typeIndex1 and mod.weightVal[typeIndex1] > 0) or (typeIndex2 and mod.weightVal[typeIndex2] > 0) or (not typeIndex1 and not typeIndex2 and baseIndex and mod.weightVal[baseIndex] > 0) + or (not typeIndex1 and not typeIndex2 and not baseIndex and poolIndex and mod.weightVal[poolIndex] > 0) end local getVeiledMods = function (veiledPool, baseType, specificType1, specificType2) local veiledMods = { } + local poolKey = veiledPool == "catarina" and "catarina_veiled_prefix" or nil for veiledModIndex, veiledMod in pairs(data.veiledMods) do - if veiledModIsActive(veiledMod, baseType, specificType1, specificType2) then + if veiledModIsActive(veiledMod, baseType, specificType1, specificType2, poolKey) then local veiledName = parseVeiledModName(veiledModIndex) veiledName = "("..veiledMod.type..") "..veiledName @@ -112,9 +117,9 @@ local caneOfKulemak = { "Has Alt Variant Two: true", "Has Alt Variant Three: true", "Selected Variant: 1", - "Selected Alt Variant: 2", - "Selected Alt Variant Two: 20", - "Selected Alt Variant Three: 21", + "Selected Alt Variant: 3", + "Selected Alt Variant Two: 25", + "Selected Alt Variant Three: 26", } for _, mod in pairs(caneOfKulemakMods) do From 471872cf921de21bbb5d8eb1e98485c5e537b414 Mon Sep 17 00:00:00 2001 From: maven Date: Sun, 26 Jul 2026 21:28:42 -1000 Subject: [PATCH 3/3] removed docs --- ...26-cane-of-kulemak-catarina-mods-design.md | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md diff --git a/docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md b/docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md deleted file mode 100644 index d89b9d38777..00000000000 --- a/docs/superpowers/specs/2026-07-26-cane-of-kulemak-catarina-mods-design.md +++ /dev/null @@ -1,76 +0,0 @@ -# Cane of Kulemak: restore missing Catarina signature mods - -**Date:** 2026-07-26 -**Status:** Approved - -## Problem - -Cane of Kulemak is a programmatically generated unique (`src/Data/Uniques/Special/Generated.lua`). -Its variant dropdowns are built by filtering `data.veiledMods` (`src/Data/ModVeiled.lua`) through -`veiledModIsActive`, which only accepts a mod if `weapon`, `staff`, or `two_hand_weapon` appears in -the mod's `weightKey` with a positive weight. - -Catarina's signature veiled mods do not use those keys. They carry their spawn weight on -`catarina_veiled_prefix` (weight 2000) and list only the slots they are *excluded* from (weight 0). -A staff is not in their exclusion lists, so in game they can roll on Cane of Kulemak, but PoB's -filter drops them. Five weapon-legal variants are missing from the item: - -1. `JunMasterVeiledLocalIncreaseSocketedSupportGemLevel` — `+2 to Level of Socketed Support Gems` - / `+(5-8)% to Quality of Socketed Support Gems` (single mod, two lines — the motivating case) -2. `JunMasterVeiledChaosExplosionOnKill` — enemies killed have (15-25)% chance to explode -3. `JunMasterVeiledSupportedByCastOnCritAndSpellDamage` -4. `JunMasterVeiledSupportedByCastWhileChannelingAndSpellDamage` -5. `JunMasterVeiledSupportedByArcaneSurgeAndSpellDamage` - -The other ~23 Catarina mods must remain excluded: they explicitly zero the `weapon` key. - -## Scope - -Cane of Kulemak only. Paradoxica and Replica Paradoxica generation must be byte-for-byte -unchanged (Replica Paradoxica's analogous gap with master signature mods is a possible follow-up, -out of scope here). - -## Design - -### Filter change (`src/Data/Uniques/Special/Generated.lua`) - -- Add an optional `poolKey` parameter to `veiledModIsActive(mod, baseType, specificType1, - specificType2, poolKey)`. -- New rule, appended to the existing checks: if **none** of `baseType`/`specificType1`/ - `specificType2` appear in the mod's `weightKey`, but `poolKey` does with weight > 0, the mod is - active. -- Explicit slot keys keep priority: a mod listing `weapon` with weight 0 stays inactive regardless - of pool key (this is what keeps the minion/spectre/flask Catarina mods off the staff, and it - mirrors the game's first-matching-weightKey-wins semantics). -- `getVeiledMods` passes `"catarina_veiled_prefix"` as `poolKey` only when - `veiledPool == "catarina"`. A nil `poolKey` preserves old behavior exactly. - -### Knock-on effects - -- The five new variants are inserted into the alphabetically sorted variant list, shifting the - indices of later variants. The hardcoded `Selected Variant` / `Selected Alt Variant` defaults - (`Generated.lua` lines 114–117) must be re-pointed so the same default mods stay selected. - Verify by generating the item text before and after. -- No new mod data is needed. Both target lines are already flagged scalable in - `src/Data/ModScalability.lua`, so the staff's `(50-70)% increased Unveiled Modifier magnitudes` - implicit scales them automatically via existing machinery. - -### Testing - -- New busted spec (in `spec/System/`, following existing `Test*_spec.lua` conventions) asserting: - - the generated Cane of Kulemak item text contains `+2 to Level of Socketed Support Gems` and - `+(5-8)% to Quality of Socketed Support Gems`; - - a weapon-illegal Catarina mod (e.g. `+1 to maximum number of Spectres`) is still absent; - - Paradoxica and Replica Paradoxica generated text is unchanged (no Catarina signature mods - leak in). -- Run the full spec suite to confirm no regressions. - -### Error handling - -None needed — pure data generation at program load; no user input or runtime failure paths. - -## Success criteria - -Selecting Cane of Kulemak from the unique list in PoB offers -"(Prefix) Increase Socketed Support Gem Level" (support gem level + quality) as a selectable -variant in the variant dropdowns, with values that scale with the unveiled-magnitude implicit.