dev #7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Groundwork for the Optimize feature. Progression is gradual — a static three weeks into a tier may be farming M1-M2 while still progging M3 — and which floors are actually clearable is the single biggest input to any planning maths, because book income is per-floor: * a player accrues one book per week only for floors in this set; * needs whose floor is absent are unreachable rather than merely slow, and have to be reported that way instead of given a fabricated ETA; * the floor-4 conversion (F4 books trade 1:1 into F1-F3 books, the main relief valve for someone stuck on one slot) only exists while floor 4 is clearable. Stored on LootRoadmapSettings, which is already the per-static roadmap row. Empty means "not configured" and every reader expands it to all four floors — so existing statics keep today's behaviour, and an unconfigured static stays distinguishable from one a lead deliberately set to all four. Gated at Lead rather than Member, unlike SetMasterPriorityList: this is a statement about the group's progression, not a personal preference, and it moves everyone's projected timeline. The migration sets defaultValueSql '{}' deliberately. ADD COLUMN ... NOT NULL with no default fails outright against a table that already has rows, and lootroadmapsettings is populated in production where migrations auto-apply at startup — so without it this would have crash-looped the API on deploy rather than failing at review time. Input is normalised, not rejected: the UI sends checkbox state, so order and duplicates carry no meaning and values outside 1-4 are not floors.GET api/LootRoadmap/Optimize/{staticUuid} suggests a loot order that reaches full BiS in the fewest weeks, plus a per-player book breakdown. Shape of the problem, which drives the design: * Chest drops are random, so a week-by-week script would be fiction. What is decidable is the priority order per loot type — who takes a Body coffer if one drops — which is what the roadmap already stores, so Apply can reuse SetOverride unchanged. * Book income is not random: one book per clearable floor per week. That gives every player a guaranteed worst-case finish date, and a computable timeline. Drops only ever pull it in, so the estimate is a ceiling rather than a guess. Objective is makespan — the last player's finish week — so a drop goes to whoever has the most outstanding book cost on that item's floor, since that is whose finish date it actually moves. Ties fall back to the static's curated master list rather than the optimizer inventing a winner. Two timelines are always returned: at today's floors, and as if all four were open. Asking a lead to predict when they will kill the next floor would be inventing a number; the gap between the two bounds says what progression is costing without pretending to know. Needs on an unclearable floor return null rather than a large number — no books accrue and no chest can drop there, so any week count would be fabricated. Those queues are still emitted, flagged unreachable, so they are ready the week the floor opens. Book prices move to RaidTier (nullable, falling back to the Dawntrail-era values in SavageBookRules). They are balance numbers Square Enix has changed between tiers, so hardcoding them would quietly produce wrong plans a patch after nobody is looking. The floor-per-slot map lives there too rather than being duplicated a third time alongside StaticController's loot breakdown. Read-only by design: it returns a proposal and never writes, so a run cannot silently overwrite an order the static agreed on. Tests cover the floor-4 conversion (a lone 6-book body piece lands week 3, not week 6, because floor-4 books convert once no weapon is owed), unreachable floors, the makespan ordering, curated tie-breaks, alt exclusion, and tier price overrides.The simulation started every player at zero, which for a static mid-tier was systematically pessimistic by up to one book per floor per elapsed week. It also skewed the ordering: someone sitting on five M3 books will buy the piece regardless, so a coffer is worth far more to a player at zero, and the makespan heuristic could not see that. Balances are hand-entered (PlayerBookBalance, unique per player and floor, Member-gated so a lead can keep the whole roster current). Keyed to a player rather than an account because books live in a character's inventory, exactly like gear. Staleness is the known weakness of hand-entered data, so it is treated as part of the record rather than hidden: every row carries UpdatedAtUtc, stamped even when the number is unchanged, since re-confirming a balance is the signal that it is still true this week. Callers are expected to surface its age. Missing rows mean zero, so a half-filled roster degrades to the previous worst-case plan instead of refusing to produce one. Two behaviours worth noting, both covered by tests: * A player who can already afford everything reports zero weeks, not one — they are waiting on a vendor trip, not a clear. * Reachability is now judged after spending what is banked. A locked floor stops income, not spending, so someone holding enough books can buy the piece today regardless of whether the group can still kill that boss. The check previously ran first and reported such a roster as unreachable when it was in fact already finished; a test caught it. SpendAndConvert was extracted so the pre-week pass and each simulated week spend by identical rules, rather than the seeded balances taking a subtly different path.PUT /api/characters/{id}/books lets the plugin report book counts read from the character's inventory, so the roadmap's estimate stops depending on someone remembering. Deliberately an additional source, not a replacement. The plugin does not exist on every platform and some players would rather not run one, so a single roster routinely mixes automatic and typed figures. PlayerBookBalance gains a Source (Manual/Plugin) to record which, because the two have very different trust profiles: an inventory read is true at a known moment, while a typed figure drifts by one per cleared floor per week. Last write still wins in both directions — a player who stops running the plugin can type over their own numbers rather than being locked out, and a hand edit takes the row back to Manual so its origin is not misreported. Floors absent from a payload are left untouched rather than zeroed: a partial sync is far likelier than a character genuinely holding none of a floor's books, and wrongly zeroing would silently inflate the estimate. A floor sent as zero is still honoured, since spending down to zero is real. The plugin sends floor totals rather than raw item ids because it is the side that can ship support for a new tier's books without an API deployment — the server stores no book item ids, so mapping here would need a config table that goes stale exactly when a tier launches. Broadcasts UpdateLootRoadmap so an open Optimize panel refreshes for the whole static the moment anyone's plugin syncs. Covered by extending the existing device-flow test: two floors sync and land as Plugin, the untouched floors stay absent, and a follow-up sync of a single floor overwrites only that one.