Exploratory: Weighted buckets manual mode - #629
Draft
noahm wants to merge 5 commits into
Draft
Conversation
Replaces the `useWeights` boolean with a three-way `bucketMode`
(`none` / `auto` / `manual`) and adds a manual bucket editor where users
author each bucket's start, end, and weight by hand. In manual mode the
top-level Lvl Min/Lvl Max controls go away, since the buckets carry
their own bounds.
The main goal was to add this without growing the draw logic, so the
modes are normalized before the draw ever sees them:
- `src/draw-buckets.ts` owns bucket layout. `getDrawBuckets` turns any
mode into one `DrawBucket[]` (a lvl range plus a weight), and
`planDraw` turns that into a `DrawPlan` (a deck plus a min/max per
bucket). Nothing downstream branches on mode, weights, or lvl bounds.
- `card-draw.ts` now only collects charts per bucket and executes a
plan. `draw()` dropped from ~190 lines to ~40, and the whole file from
429 to 307. Difficulty lvl is applied in exactly one place
(`drawableBucketForChart`) rather than as a raw range check plus a
separate zero-weight filter.
- Buckets are `{low, high}` ranges everywhere, retiring the
`number | [low, high]` union and its two matching rules.
- `planDraw` is the seam for the planned flat-count-per-bucket mode: it
already emits min/max, so only that function needs to learn the new
strategy.
The draw loop is now constructive (cover minimums, then fill from the
weighted deck) instead of rejection sampling. The old
`do { ... } while (redraw)` could never terminate when a bucket's forced
minimum exceeded the charts available to it, which manual buckets make
trivial to hit; the new loop always terminates and returns a short
drawing instead, which the existing "couldn't draw enough charts" guard
already handles.
Other notes:
- Manual buckets match in list order, first hit wins, so a chart is
never drawable twice. Overlaps are surfaced with a warning.
- Each manual bucket row shows its actual pool size, so empty or
over-subscribed buckets are visible before drawing.
- Switching into manual mode seeds from the current layout rather than
starting empty.
- Saved configs with `useWeights` migrate to the matching `bucketMode`.
- `getAvailableLevels` is now cached per game data file, since bucket
resolution calls it from render paths.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLpPRExceU5W4B37UncuFY
SDVX has whole lvls up to 17, then 17.5, then 0.1 steps from 18.0 to 20.9, with no `granularTierResolution` set. The manual editor derived its step size from that field, so it fell back to 1 and the spinner jumped 19 -> 20, skipping the entire fractional range. - Derive the step from the smallest gap between adjacent available lvls instead, which covers granular-tier games and inherently fractional ones alike. - Snap a typed bound onto a lvl the game actually has, on blur. Buckets match on a plain range so an off-lvl bound was never wrong, but it made the bucket's real extent hard to read and accumulated float noise while stepping. Replaces `clampValueOnBlur`, since a snapped value is always in range. - Show a bucket's literal bounds as a tooltip in the by-lvl-range editor when they differ from its label: a bucket labelled "18" covers 18.0-18.9 on SDVX, which was invisible before.
maimai stores chart constants (13.7) rather than the lvls players use
("13+"), and the app rendered the raw number everywhere — on cards, in
Lvl Min/Max, and in the new bucket editor. A "+" lvl is a span of
constants, not one value, so this needs more than a display swap.
- New `src/lvl-display.ts` builds a list of `LvlBand`s per game: the
label a player uses plus the range of internal values behind it. Games
whose lvls map one to one get single-value bands, so they're unchanged.
Driven by a new optional `meta.lvlPlusThreshold` rather than hardcoded
per-game logic; maimai sets 0.6.
- `LvlBoundInput` renders and parses bounds in that notation. Which end
of a band a bound resolves to depends on whether it opens or closes a
range, so a bucket from "13" to "13+" covers 13.0 through 13.9 the way
a player expects. Arrow keys step band to band, Enter commits, Escape
reverts, and unparseable text is flagged instead of coerced.
- Lvl Min/Max and song cards use the same notation, so a lvl reads the
same everywhere in the app.
- Tier-drawing games label bounds "T07", matching how the by-lvl-range
editor already showed them.
Buckets are still plain `{low, high}` numbers — none of this reaches the
draw logic, which continues to match on raw values.
Two fixes found while testing this:
- Escape inside a lvl input closed the entire settings drawer, since the
keydown propagated up to the Drawer. Cancelling an edit now stays local.
- Cards on tier-drawing games briefly rendered a mix of "T11" and "13":
bound picking enumerates tiers while a card shows the chart's own lvl,
and those are different numeric spaces. `getChartLvlBands` keeps them
apart.
This supersedes the SDVX step-size derivation from the previous commit;
bounds no longer step by a raw increment at all, they step band to band.
This reverts commit 9038266. maimai's plus lvls are a display convention in the game, but the decimal chart constants behind them are meaningful in their own right and are what the app should show and accept. Collapsing 13.0-13.9 into "13" and "13+" threw that detail away. So maimai wants the same handling as any other game with non-integer lvls: raw values, stepped and snapped by the data's own increment. That is what the preceding commit already does, generically, via the smallest gap between adjacent available lvls — for maimai that comes out to 0.1, the same as SDVX. Reverting takes the two fixes in that commit with it, but both were fixes to problems it introduced: Escape closing the settings drawer came from the keydown handling it added, and the T11/13 mixup on tier-game cards came from routing card lvls through the bound-picking band list. Neither exists without it. Also drops `meta.lvlPlusThreshold` from the schema, maimai's data file, and the maimai import script, since nothing reads it now.
Turning on granular lvls left hand-authored buckets holding whole-lvl bounds while chart matching switched to sanbai tiers, so a bucket of "13 to 13" went from every lvl 13 chart to only the few sitting exactly on 13.00. The by-lvl-range layout already redraws itself across that toggle; manual buckets now get the same treatment and land on identical spans (13.00-13.95 on DDR World), because both go through the same `topOfWholeLvl` helper rather than reimplementing the edge. Toggling back collapses bounds to whole lvls again, so a whole-lvl bucket round-trips unchanged. Adding a bucket now spans the whole lvl it opens on for the same reason: in granular mode a fresh single-tier bucket matches almost nothing. On games with inherently fractional lvls this also improves the default — adding after a bucket ending at 19.4 gives 19.5-19.9 instead of 19.5-19.5. Pool sizes dip slightly with granular lvls on (304 -> 303 for lvl 13), which is the pre-existing rule that granular mode skips charts with no sanbai tier, not an artifact of the rescale.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the current settings for weighted distributions with a separate mode selector with three options: