Skip to content

Implement Knowledge Path ROI Optimizer#1282

Open
netouss wants to merge 24 commits into
mainfrom
feat/KnowledgePathROI
Open

Implement Knowledge Path ROI Optimizer#1282
netouss wants to merge 24 commits into
mainfrom
feat/KnowledgePathROI

Conversation

@netouss

@netouss netouss commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Introduce a comprehensive Knowledge ROI module that calculates and visualizes the return on investment for unspent knowledge points across various recipes. The implementation includes a calculation engine, a user interface with sortable lists and tooltips, and integration with a weekly planner for optimal path management. Fixes address layout issues and enhance functionality for better user experience. Documentation updates reflect the completion of all phases.

@derfloh205 derfloh205 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

See comments.

Furthermore: Try to use GUTIL.FrameDistributor for any optimization process that might take a while. I get freezes when I optimize although I tested on a recipe with only 2 nodes left

Comment thread DB/knowledgeROIDB.lua Outdated

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Lets just call it KnowledgePointValue and put it into the Specialization Info Module instead of creating a separate module.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Specialization Info Module already has a list of spec nodes with tooltips.

We can trigger the different optimizations via optionsButton context menu buttons

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

We can even make it similar to the advanced craft log. An additional window that can be enabled within the spec info options that then is similar to the newly implemented one

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in c1583a3. Renamed everything to KnowledgePointValue and moved the files into the SpecializationInfo module:

  • DB/knowledgeROIDB.lua -> DB/knowledgePointValueDB.lua
  • Modules/KnowledgeROI/ -> Modules/SpecializationInfo/KnowledgePointValue.lua + KnowledgePointValueUI.lua
  • All references updated across TOC, Const, Init, Modules, ControlPanel, Locals
  • Added SavedVariables migration from old knowledgeROIDB key

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good idea! The KnowledgePointValue module now lives inside Modules/SpecializationInfo/ as KnowledgePointValue.lua + KnowledgePointValueUI.lua. The existing SpecializationInfo tooltip already hooks into the KPV data to show per-node value inline.

Regarding the options button context menu and the additional window approach (like advanced craft log) — I'll look into that as a follow-up. For now the module is integrated into SpecInfo with its own togglable frame via the Control Panel checkbox.

Comment thread Init/Init.lua Outdated
Comment on lines +407 to +409
C_Timer.After(1.5, function()
CraftSim.KNOWLEDGE_ROI:CheckAndNotifyUnspentPoints()
end)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Avoid such timed calls, Better to find an event to react to or maybe use GUTIL:WaitFor to check when the necessary info is available. The update display call for example can do the same if not yet initialized

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in c1583a3. Replaced C_Timer.After(1.5, ...) with GUTIL:WaitFor that polls for CraftSim.MODULES.recipeData ~= nil at 0.1s interval with a 5s timeout. Much more reliable than a hardcoded delay.

Comment thread plan/KnowledgePathROI.md Outdated

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Avoid commiting AI .md files into the main repo. Please use a personal global gitignore or add it to you exlude file

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in c1583a3. Removed from the repo with git rm --cached and added plan/ to .git/info/exclude. Won't happen again.

netouss pushed a commit that referenced this pull request Apr 14, 2026
…eview

Address all review comments from derfloh205 on PR #1282:

1. Rename module to KnowledgePointValue and move into SpecializationInfo:
   - DB/knowledgeROIDB.lua -> DB/knowledgePointValueDB.lua
   - Modules/KnowledgeROI/ -> Modules/SpecializationInfo/KnowledgePointValue*.lua
   - All references updated across TOC, Const, Init, Modules, ControlPanel,
     Locals, and SpecializationInfo/UI.lua

2. Use GUTIL.FrameDistributor for scan/optimize to prevent UI freezes:
   - Add FullProfessionScanAsync() distributing node iteration across frames
   - Add CalculateOptimalPathAsync() with async scan + per-step greedy loop
   - UI StartFullScan/StartOptimizePath now use async versions with progress
   - Sync versions preserved for non-UI callers

3. Replace C_Timer.After(1.5) with GUTIL:WaitFor in Init.lua:
   - Polls for CraftSim.MODULES.recipeData availability (0.1s interval, 5s max)

4. Remove plan/KnowledgePathROI.md from repo:
   - Added plan/ to .git/info/exclude

5. Add SavedVariables migration from old knowledgeROIDB key
@netouss

netouss commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator Author

All review comments addressed in c1583a3:

1. Rename & move ✅ — KnowledgeROIKnowledgePointValue, files moved into Modules/SpecializationInfo/. SavedVariables migration included for existing users.

2. GUTIL.FrameDistributor ✅ — Added FullProfessionScanAsync() that distributes node iteration across frames (3 nodes/frame), and CalculateOptimalPathAsync() that chains an async scan + per-step greedy FrameDistributor. UI StartFullScan and StartOptimizePath now use the async versions — no more UI freezes.

3. GUTIL:WaitFor ✅ — Replaced C_Timer.After(1.5) with GUTIL:WaitFor polling CraftSim.MODULES.recipeData ~= nil (0.1s interval, 5s timeout).

4. plan/*.md ✅ — Removed from repo, plan/ added to .git/info/exclude.

5. DB rename ✅ — knowledgeROIDB.luaknowledgePointValueDB.lua.

Regarding the SpecInfo options button context menu / advanced-craft-log-style window integration suggestion — happy to explore that as a follow-up iteration once this base is merged.

netouss pushed a commit that referenced this pull request Apr 14, 2026
…eview

Address all review comments from derfloh205 on PR #1282:

1. Rename module to KnowledgePointValue and move into SpecializationInfo:
   - DB/knowledgeROIDB.lua -> DB/knowledgePointValueDB.lua
   - Modules/KnowledgeROI/ -> Modules/SpecializationInfo/KnowledgePointValue*.lua
   - All references updated across TOC, Const, Init, Modules, ControlPanel,
     Locals, and SpecializationInfo/UI.lua

2. Use GUTIL.FrameDistributor for scan/optimize to prevent UI freezes:
   - Add FullProfessionScanAsync() distributing node iteration across frames
   - Add CalculateOptimalPathAsync() with async scan + per-step greedy loop
   - UI StartFullScan/StartOptimizePath now use async versions with progress
   - Sync versions preserved for non-UI callers

3. Replace C_Timer.After(1.5) with GUTIL:WaitFor in Init.lua:
   - Polls for CraftSim.MODULES.recipeData availability (0.1s interval, 5s max)

4. Remove plan/KnowledgePathROI.md from repo:
   - Added plan/ to .git/info/exclude

5. Add SavedVariables migration from old knowledgeROIDB key
@netouss netouss force-pushed the feat/KnowledgePathROI branch from c1583a3 to c1f2607 Compare April 14, 2026 18:56
@netouss

netouss commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator Author

Answers to Genju's questions

What KP amount does the weekly plan optimize?
Does it consider currently not used points the player has?

Yes — it auto-detects your unspent points. The implementation calls C_ProfSpecs.GetCurrencyInfoForSkillLine(skillLineID) and reads currencyInfo.numAvailable, which is the number of knowledge points you currently have available but haven't spent yet. That number is used directly as numPoints for the greedy path calculation.

If the API returns 0 (e.g. you spent all your points or the info isn't loaded yet), the algorithm falls back to planning 5 points ahead — a conservative default that still gives a useful preview.

New in the latest commit: the Optimize tab now shows a KP input field that is pre-filled with your detected unspent points. You can edit it to plan any custom number (e.g. "how should I spend 20 points over the next reset cycle?") before clicking Optimize or Weekly.


Performance note (GUTIL.FrameDistributor)

Both long-running operations already use GUTIL.FrameDistributor to avoid UI freezes:

  • Full Scan — processes 3 profession nodes per frame tick, reports progress live
  • Optimize Path (greedy phase) — allocates 1 greedy step per frame tick, reports step progress live

No C_Timer.After busy-waiting anywhere in the critical path.

netouss pushed a commit that referenced this pull request Apr 18, 2026
…eview

Address all review comments from derfloh205 on PR #1282:

1. Rename module to KnowledgePointValue and move into SpecializationInfo:
   - DB/knowledgeROIDB.lua -> DB/knowledgePointValueDB.lua
   - Modules/KnowledgeROI/ -> Modules/SpecializationInfo/KnowledgePointValue*.lua
   - All references updated across TOC, Const, Init, Modules, ControlPanel,
     Locals, and SpecializationInfo/UI.lua

2. Use GUTIL.FrameDistributor for scan/optimize to prevent UI freezes:
   - Add FullProfessionScanAsync() distributing node iteration across frames
   - Add CalculateOptimalPathAsync() with async scan + per-step greedy loop
   - UI StartFullScan/StartOptimizePath now use async versions with progress
   - Sync versions preserved for non-UI callers

3. Replace C_Timer.After(1.5) with GUTIL:WaitFor in Init.lua:
   - Polls for CraftSim.MODULES.recipeData availability (0.1s interval, 5s max)

4. Remove plan/KnowledgePathROI.md from repo:
   - Added plan/ to .git/info/exclude

5. Add SavedVariables migration from old knowledgeROIDB key
@netouss netouss force-pushed the feat/KnowledgePathROI branch from aa990bc to b4e6c83 Compare April 18, 2026 17:38
@netouss netouss self-assigned this Apr 18, 2026
@netouss netouss requested a review from derfloh205 April 18, 2026 17:40
netouss added 18 commits April 25, 2026 10:40
- Add KnowledgeROI module with single-recipe and full-profession scan modes
- Single-recipe mode: calculates profit delta per knowledge point for each
  unspent specialization node affecting the current recipe
- Full-profession scan: iterates all known recipes and all base nodes to
  produce a global ranking of knowledge point ROI across the profession
- Add GGUI-based UI with sortable node list showing icon, name, rank, and
  gold-per-point ROI with color-coded positive/negative values
- Add detailed tooltips with remaining points, total estimated ROI, and
  top affected recipes (for full scan results)
- Add DB repository (knowledgeROIDB) for caching full scan results with
  7-day auto-cleanup
- Integrate into Modules.lua show/hide/update lifecycle
- Add Control Panel toggle checkbox for module visibility
- Register frame constants (KNOWLEDGE_ROI, KNOWLEDGE_ROI_WO) and
  option constant (MODULE_KNOWLEDGE_ROI) in Const.lua
- Add enUS localization entries for control panel label and tooltip

New files:
  DB/knowledgeROIDB.lua
  Modules/KnowledgeROI/KnowledgeROI.lua
  Modules/KnowledgeROI/UI.lua

Modified files:
  CraftSim.toc, Init/Init.lua, Locals/enUS.lua,
  Modules/ControlPanel/UI.lua, Modules/Modules.lua, Util/Const.lua
…orithm

- Add GetAvailableKnowledgePoints() via C_ProfSpecs.GetCurrencyInfoForSkillLine
- Add CalculateOptimalPath() greedy algorithm with lazy node recalculation
- Add RecalculateNodeROIAtVirtualRank() for simulating ROI at non-real ranks
- Add CalculateRecipeNodeDeltaAtVirtualRank() for virtual rank profit deltas
- Add 'Optimize' button (side-by-side with 'Full Scan')
- Add PathStep display with step numbering and rank arrow notation
- Add cumulative ROI in roadmap tooltips
- Show available knowledge points in all modes
- Default to planning 5 points ahead when no points available
- Fix layout: anchor nodeList to frame.content (TOPLEFT -55) instead of
  modeText.frame whose height is ~0px, causing column headers overlap
- Fix 0 nodes in full scan: fall back to recipeMapping keys when
  cachedRecipeIDs is empty (user never did a Recipe Scan)
- Fix 0 nodes: include rank -1 nodes (not yet unlocked) in full scan
  filter - was excluding them with 'currentRank >= 0'
- Fix -1 display: show max(0, rank) in rank column and tooltips
- Move configID lookup outside per-node loop (minor perf improvement)
- Apply same cachedRecipeIDs fallback in CalculateOptimalPath
…ntegration

- Add ROI heatmap row backgrounds: green gradient for positive ROI,
  red gradient for negative ROI, intensity scales with magnitude
- Add '★ Next Best' legendary-colored badge on first positive-ROI row
- Inject ROI data into Blizzard spec tree node tooltips via existing
  ProfessionSpecs.SpecPathEntered hook (shows ROI/pt, total remaining,
  and top 3 affected recipes from cached full-scan data)
- Add background texture per row in rowConstructor (WHITE8X8, BACKGROUND layer)
- Normalize ROI color intensity across the full result range
- Add 'Weekly' button that loads saved optimal path instantly from DB
  (falls back to running Optimize if no saved plan or plan is stale >3d)
- Save optimal path as WeeklyPlan in DB on every Optimize run
  (persists across sessions: path steps, total gain, available points)
- Add summary text at bottom of frame showing plan total gain + age
- Add unspent knowledge points chat notification on ProfessionsFrame open
  (once per session per profession, includes best node recommendation)
- Add DB methods: SaveWeeklyPlan, GetWeeklyPlan with WeeklyPlan type
- Add KnowledgeROI methods: CheckAndNotifyUnspentPoints, SaveWeeklyPlan,
  GetSavedWeeklyPlan (with 3-day staleness check)
- Reduce nodeList height from 295 to 275 to accommodate summary text
… row

- Move Weekly/Optimize/Full Scan buttons from offsetY -26 to -50
  (below mode text, above column headers)
- Push nodeList from offsetY -55 to -78 (below buttons)
- Reduce nodeList sizeY from 275 to 250 to fit
Layout fixes:
- Increase frame size from 350x400 to 370x430 for more room
- Move modeText from offsetY -30 to -15 (closer to title)
- Move buttons (Weekly/Optimize/Full Scan) from -50 to -35
- Push nodeList from -78 to -80 with sizeY 300 (was 250)
- Ensures clear separation: title > modeText > buttons > headers > list

Calculation fixes:
- Guard configID == 0 (truthy in Lua but invalid for C_Traits API)
  treating it as nil to prevent silent failures
- When nodeInfo is unavailable (no configID), assume currentRank = 0
  instead of skipping the node entirely
- Remove totalDelta ~= 0 filter: include all investable nodes even
  with 0 ROI (shows them as neutral rather than hiding)
- Fix remainingRanks to use math.max(0, currentRank) to avoid
  negative rank arithmetic
- Add configID == 0 guard to GetNodeName as well
…line

- Log recipeMapping count, nodeToRecipes count, baseNodes count
- Log configID, skillLineID, cachedRecipeSet size
- Log first 3 nodes in detail: rank, affectedRecipes, nodeInfo status
- Log per-node recipe stats: tried, skipped, errored, totalDelta
- Log errors from pcall with actual error message
- Log CalculateRecipeNodeDelta exit points: no recipeInfo, RecipeData
  creation failure, no specializationData, node not found, at maxRank
- Log final summary: total results vs non-zero ROI count
- All via CraftSim.DEBUG:RegisterDebugID (only shown when enabled)
UI layout fix:
- modeText offsetY: -15 -> -30 (clear of GGUI title bar ~25px)
- buttons offsetY: -35 -> -55 (below modeText + gap)
- nodeList offsetY: -80 -> -83 (below buttons 22px + gap)
- Root cause: frame.content TOP = frame TOP in GGUI, so title bar
  overlaps first ~25px of content area

Debug additions for delta=0 investigation:
- Log gathering/dummy recipe filter
- Log !supportsCraftingStats filter
- Log actual baseProfit/newProfit/delta values on successful computation
- These will reveal why all recipes return delta=0 despite rank bump
ROOT CAUSE of delta=0:
RecipeData:UpdateProfessionStats() rebuilds professionStats from
baseProfessionStats (Blizzard API, frozen at current ranks) + gear +
buffs + specData:GetExtraValues(). The spec base stat contributions
(skill, multicraft.value, resourcefulness.value, etc.) are already
baked into baseProfessionStats. Calling specData:UpdateProfessionStats()
updates the node's own professionStats correctly, but
recipe:Update() never reads those base values — only GetExtraValues().

FIX (stat-delta approach):
Instead of calling recipe:Update() (which would overwrite everything),
we now:
1. Capture the node's professionStats BEFORE the rank bump
2. Bump rank, call nodeData:Update() to recalculate node stats
3. Compute the delta (new - old) for all stats + extraValues
4. Apply this delta directly to recipe.professionStats
5. Call resultData:Update() + priceData:Update() + GetAverageProfit()

Applied to all 3 simulation functions:
- SimulateNodeRankIncrease (single recipe mode)
- CalculateRecipeNodeDelta (full scan mode)
- CalculateRecipeNodeDeltaAtVirtualRank (optimize path mode)

UI layout fix:
- Frame height: 430 -> 460
- Buttons Y: -55 -> -50 (right after modeText)
- NodeList Y: -83 -> -100 (28px gap from button bottom for headers)
- NodeList sizeY: 300 -> 310 (use extra frame height)
The Unicode ★ character was not visible in WoW's game font.
Replace all 4 occurrences with CreateAtlasMarkup('loottoast-star', 14, 14)
which renders as a proper golden star icon in-game:
- Next Best badge on first row of node list
- Summary text in Optimize mode
- Summary text in Weekly Plan mode (both fresh and saved)
WoW's default font does not render Unicode chars like arrows and
em-dashes. Replace in user-visible strings:
- UI.lua: rank column arrow -> (was U+2192), em-dash - (was U+2014)
- KnowledgeROI.lua: chat notification em-dashes - (was U+2014)
Code comments left as-is (not rendered in-game).
loottoast-star atlas was not rendering in-game. Replace with
PetJournal-FavoritesIcon which is already used successfully in
CraftSim's RecipeScan module (golden star, 14x14).
Instead of simulating +1 rank (which rarely crosses quality thresholds),
bump nodes to maxRank to capture ALL threshold crossings and perk unlocks.
ROI per point = totalDelta / remainingRanks (averaged).

Also cleans up verbose per-recipe debug prints, keeping concise node-level
logs routed through CraftSim.DEBUG:RegisterDebugID.
…eview

Address all review comments from derfloh205 on PR #1282:

1. Rename module to KnowledgePointValue and move into SpecializationInfo:
   - DB/knowledgeROIDB.lua -> DB/knowledgePointValueDB.lua
   - Modules/KnowledgeROI/ -> Modules/SpecializationInfo/KnowledgePointValue*.lua
   - All references updated across TOC, Const, Init, Modules, ControlPanel,
     Locals, and SpecializationInfo/UI.lua

2. Use GUTIL.FrameDistributor for scan/optimize to prevent UI freezes:
   - Add FullProfessionScanAsync() distributing node iteration across frames
   - Add CalculateOptimalPathAsync() with async scan + per-step greedy loop
   - UI StartFullScan/StartOptimizePath now use async versions with progress
   - Sync versions preserved for non-UI callers

3. Replace C_Timer.After(1.5) with GUTIL:WaitFor in Init.lua:
   - Polls for CraftSim.MODULES.recipeData availability (0.1s interval, 5s max)

4. Remove plan/KnowledgePathROI.md from repo:
   - Added plan/ to .git/info/exclude

5. Add SavedVariables migration from old knowledgeROIDB key
netouss added 5 commits April 25, 2026 10:40
- Remove standalone KNOWLEDGE_POINT_VALUE frames (both WO and non-WO)
- Widen SpecInfo frame 290->390 px to host the 3-column node list
- Add Info/Optimize tab buttons; Info tab = existing node-rank list,
  Optimize tab = KPV scan results with Full Scan / Optimize / Weekly
- Add KP numeric input (auto-fills detected available points, user-editable)
  to allow planning any number of custom points via Optimize button
- KnowledgePointValueUI: remove Init(); all Start* functions now receive
  the optimizeContent sub-frame from the button callback (no frame ID lookups)
- UpdateDisplay now called from UpdateInfo so Optimize tab refreshes on
  recipe switch without user action
- CheckAndNotifyUnspentPoints now gates on MODULE_SPEC_INFO (KPV is part
  of SpecInfo, no longer a separate opt-in module)
- Remove MODULE_KNOWLEDGE_POINT_VALUE option, its ControlPanel checkbox,
  FRAMES.KNOWLEDGE_POINT_VALUE* consts, and all Modules.lua KPV frame blocks

Answers Genju's Discord questions:
- Weekly plan auto-detects unspent KP via C_ProfSpecs API; falls back to 5
- New KP input lets the player specify any count before clicking Optimize
- 'Weekly' renamed 'Load Plan' (clearer: loads saved path, no recalc)
- Tooltips on all three buttons explaining their role:
  Full Scan   = calculates ROI table for all nodes across all recipes
  Optimize    = full scan + greedy path, saves result to DB
  Load Plan   = instant replay of saved path (falls back to Optimize if none)
… anchor

- Replace manual GGUI.Button tabs with GGUI.BlizzardTab (PanelTopTabButtonTemplate)
  -> vrais onglets WoW avec select/deselect visuels natifs
- Fix GGUI.NumericInput anchor: was using anchorPoints (GGUI.Button API, ignored by
  NumericInput) -> now uses anchorParent/anchorA/anchorB directly, fixing the input
  appearing in the center of the screen
- Align KP input and action buttons on same row (offsetY=-58/-54)
- nodeList sizeY 295->290, offsetY -80->-85 to match new row positioning
- Backward compat preserved: frame.content.infoContent and .optimizeContent still
  point to the correct tab content frames
…are wired

GGUI.Button only calls SetTooltipsByTooltipOptions() if tooltipOptions is
passed in the constructor options. Assigning .tooltipOptions after the fact
leaves the button without OnEnter/OnLeave scripts -> no tooltip shown.
@netouss netouss force-pushed the feat/KnowledgePathROI branch from b4e6c83 to a92b715 Compare April 25, 2026 08:40
Copilot AI review requested due to automatic review settings April 28, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants