Skip to content

Loadout Optimizer: stat-target planner — suggest armor to farm to meet stat goals (#11832) - #11940

Draft
Reuzehagel wants to merge 11 commits into
DestinyItemManager:masterfrom
Reuzehagel:stat-planner-prototype
Draft

Loadout Optimizer: stat-target planner — suggest armor to farm to meet stat goals (#11832)#11940
Reuzehagel wants to merge 11 commits into
DestinyItemManager:masterfrom
Reuzehagel:stat-planner-prototype

Conversation

@Reuzehagel

Copy link
Copy Markdown

Changelog: Loadout Optimizer can now suggest which armor to farm to meet your stat goals.

What this is

When your stat targets aren't buildable from owned armor, a new opt-in "Armor to Farm" section in the Loadout Optimizer tells you the minimum set of new drops that would get you there. E.g. "keep these 2 pieces, farm 3× Powerhouse (Class tertiary), with at least 3 from [set]", including the stat mods and tuning to slot. When the targets are unreachable even with perfect drops, it says so, and shows how far off you are.

This implements the toggle discussed when #11832 was closed, and in the #i-have-an-idea Discord thread.

image

Combinatorics concern

The original issue was closed over combinatorial blowup, but the search space is small, because a future Armor 3.0 legendary's stat block is fully determined by (archetype, tertiary stat) at a given tier. That's 12 archetypes × 4 tertiaries = 48 stat-distinct pieces per slot, and the same 48 in every slot.

Since the pieces are interchangeable across slots, a candidate set is a choice of 5 from 48 with repetition, unordered: C(48+5-1, 5) = C(52,5) ≈ 2.6M, not 48⁵ ≈ 255M ordered ones. And because Destiny 2 is no longer receiving content updates, that archetype table is frozen, so it can't grow or rot.

Measured (Jest, single thread, full space, exact, no sampling):

  • Ideal-composition search over all 2,598,960 compositions: ~1.9s, and it runs in its own web worker, so the UI doesn't block. Replans are much faster because the minimum-acquisition search exits at the first feasible farm count.
  • Cross-validation: for a battery of targets, the planner's recipe fed into the unmodified LO worker produces valid sets, and planner-unreachable targets produce zero worker sets.

How it works

  • hypothetical-items.ts: archetype stat model (from a local 12-entry table plus the user's own items), the multiset enumerator, and the minimum-acquisition search (keep owned pieces where possible, farm the rest; first feasible farm count wins).
  • planner.ts: pure orchestration over structured-cloneable data (exotic modes, pins, set bonuses), run in a dedicated web worker (PlannerWorker.ts) via comlink, mirroring the LO worker setup.
  • HypotheticalPlanner.tsx: the UI section. The opt-in checkbox gates all computation.
  • The real LO worker stays ground truth: if it finds sets from owned armor, the planner defers to it ("already buildable") since it models things the planner doesn't, like artifice and every exotic copy.
  • Feature flag loFarmingPlanner = !release. Everything is additive; the only touch outside the new folder is a 17-line hookup in LoadoutBuilder.tsx.

Modeled / not modeled

Modeled: subclass and locked mod stats, auto +10/+5 general mods with real energy costs, locked general/bucket-specific mods claiming sockets and energy, set bonus requirements, farmed-piece tuning (+5 with a dump stat), owned-piece tuning variants, exotics (locked, Any Exotic, or farm-a-better-copy).

Not modeled: artifice, per-piece activity mod energy, exotic re-rolls. Archetype identities come from a hand-checked local table, with description parsing as a fallback for non-English manifests. Since the game is frozen, that's static data.

Tests

  • 23 synthetic-fixture unit tests (no manifest): pins, set-bonus deficits, ideal-bound fallback, minor-vs-major mods, energy-constrained mods, exotic modes.
  • 6 manifest tests: model validation against every owned Armor 3.0 legendary, the benchmarks above, and planner/worker cross-validation.

Disclosure

I used Claude Code for a large portion of this work. The idea, the (archetype × tertiary) -> 48-blocks insight, the UX decisions, and the verification are mine. I'm happy to walk through any part of the code and to make whatever changes you'd like.

Open questions for review

  • UI placement: considering moving it into the empty right-hand panel next to "No builds were found" (per the Discord thread).
  • I've dropped the "(Prototype)" label (the feature flag already says that) but kept the debug timing line while it's flagged. Happy to remove that too.

…nager#11832)

Feasibility spike for a planner that tells you what armor to farm to
hit stat targets. Derives the Armor 3.0 archetype stat model
empirically from owned items, enumerates the ~24 stat-distinct
hypothetical pieces per slot, and benchmarks both a multiset
enumerator (98k combos, ~150ms) and the unmodified LO worker
(24^5 = 7.96M combos, ~10s) over that space.
The mid-2026 update added six archetypes (Siegebreaker, Skirmisher,
Demolitionist, Colossus, Reaver, Powerhouse). Deriving the model only
from owned items would silently miss archetypes the user has never
had drop, so merge in archetype identities from the manifest plugs.
Their primary/secondary stats exist only as localized description
text, so the parser is en-only; production should generate this
table in d2ai. Space grows to 48 blocks/slot; the multiset
enumerator covers C(52,5)=2.6M compositions in ~3.5s.
Adds a collapsible menu section below Energy Options that plans over
hypothetical tier-5 drops for the current stat minimums: verdict
(reachable or short by N points), the archetype/tertiary recipe with
counts and icons, and the auto stat mods it assumed. Recomputes as
stat constraints change, deferred so slider drags stay responsive.
The enumerator inner loop now hoists partial sums and skips ignored
stats so a full 48-block plan runs in well under a second.
The Armor to Farm section now answers "what do I still need": it
fixes the locked exotic to your best owned copy, keeps as many owned
pieces as possible (searching farm-counts ascending), counts owned
set pieces toward required set bonuses (farmed pieces cover the
deficit, since all archetypes drop from all sources), and folds in
subclass/mod stat contributions like the real worker does. A "Keep
armor I already own" toggle switches between minimum-farm mode and
pure ideal-drops theorycrafting.
"Combined with your:" now shows which roll each kept piece is, e.g.
"Geomag Stabilizers (Grenadier / Class)", so duplicate copies are
distinguishable. With the keep-owned toggle off, the verdict drops
the word "new" since every piece is hypothetical.
Correctness:
- Fix the "from set X" line rendering blank: passing count made
  i18next resolve scanner-generated empty plural keys; use a
  non-plural interpolation instead.
- Fix block pruning dropping whole archetypes: every block has the
  same stat total so relevance sorts tied and kept insertion order;
  prune by per-archetype round-robin with ignored-tertiary dedupe,
  and bound the search with an exact ideal-drops pass over the FULL
  block list so pruning can never produce a false "unreachable".
- Honor pinned items as required keeps; stop dropping pinned exotics.
- Reserve the exotic slot (with a note) when the locked exotic has no
  available copy; select the exotic from its bucket so name-matched
  reissues work like item-filter does.
- Mirror the worker's mod budget: auto mods use only general sockets
  left by locked general mods, and none when auto stat mods is off.
- Show the kept pieces under an unreachable verdict so "Closest:"
  isn't followed by nothing; per-mode fine print with the real model
  tier instead of hardcoded "Tier 5".
- Guard stat-less items out of candidate mapping (NaN poisoning).
- Seed archetype identities from the manifest before item scanning so
  one odd roll can't poison an archetype.

Performance:
- Ideal-bound short-circuit caps the previously unbounded search on
  unreachable targets; upper-bound pruning skips hopeless keep-slot
  subsets; recursion uses per-depth scratch arrays (no allocation);
  multiset sums build lazily per farm-count.
- Hoist per-item stat mapping out of the plan memo; use
  calculateAssumedMasterworkStats directly; memoize the manifest
  archetype scan per-defs.

Cleanup: shared greedy-mod/tally/zero-stats helpers between planners,
mapValues/sumBy/comparator reuse, test uses pruneBlocksForTargets
instead of reimplementing it.
- Stat mods now model +5 minors and per-piece energy: major/minor general
  mod costs (from the auto-mod defs), locked general mods claiming sockets
  and energy, and per-piece budgets (10 minus bucket-specific mod costs;
  owned pieces use their assumed energy). Fast path skips the bookkeeping
  when no cost can exceed any budget, keeping the hot loop unchanged.
- 'Any Exotic' plans each slot with the user's best owned exotic there and
  takes the best outcome, instead of quietly planning 5 legendaries.
  Pinned exotics decide the slot; no owned exotic gets an explanatory note.
- Archetype primary/secondary stats now come from a generated
  armor-archetypes.json (d2ai-style; companion generator drafted in
  d2-additional-info). English description parsing remains only as a
  fallback for archetypes newer than the table.
- Synthetic-fixture unit tests for planMinimumAcquisitions: pins,
  set-bonus deficits + graceful degrade, ideal-bound fallback, and
  mod/energy accounting.
- Gated behind $featureFlags.loFarmingPlanner (beta/dev only).
The scanner can't extract keys from a computed ternary inside t(), so
FarmingPlannerFinePrint/-Ideal never made it into the generated
en.json and the runtime showed the key path. Split into two explicit
t() calls.
…ility

- Kept pieces (exotic included) render as real draggable item tiles with
  click-through to the item popup, instead of a text list.
- When the Loadout Optimizer worker already found sets meeting the targets
  from owned armor, show 'already buildable' instead of a farm recipe. The
  worker models tuning mods, artifice sockets and every exotic copy, which
  the planner's model deliberately doesn't — so it's ground truth for owned
  armor and this removes a class of false 'you need N pieces' verdicts.
- planner.ts: the full planning orchestration (owned-candidate selection,
  exotic modes including Any Exotic) extracted into one pure function over
  structured-cloneable inputs. Pieces reference DimItems by id only; the
  component maps kept ids back to items for the tiles.
- PlannerWorker.ts exposes it via comlink, same pattern as the LO worker.
- usePlannerWorker hook: recompute on input change, keep the last plan
  while a new one runs, terminate a still-running stale computation rather
  than queueing behind it, clean up on unmount.
- New unit tests for the orchestration: locked-exotic missing/best-copy,
  Any Exotic slot choice, no-exotic degrade, pinned-exotic override.
- Opt-in checkbox gates all planner work; the archetype model is only
  derived once enabled (was a full item+manifest scan on every LO mount)
- Move armor-archetypes.json out of src/data/d2 (d2ai regenerates that
  dir) next to the planner that consumes it
- Farmed-piece tuning strings; fine print no longer claims tuning is ignored
- Share one ideal-bound computation across Any Exotic farmed candidates;
  hoist owned-candidate scoring out of the per-exotic loop
- Dedup helpers (budget consumption, scratch refill), reuse existing
  exports (tuningStatBoost, totalFarmCount, zeroArmorStats, maxBy,
  Map.groupBy), flatten the verdict/render logic
- Fix stale doc comments (tuning mods are modeled, not ignored) and drop
  reviewer-addressed comments
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