-
released this
2026-09-05 20:20:05 -05:00 | 24 commits to main since this releaseFixed
- A build the Item Import panel refused to show cost a card slot instead of
moving down the list. Both item adapters truncatedcoreto the top
MAX_ITEM_OPTIONSand validated afterwards, so a row that could not be
rendered left a hole rather than yielding to the next real build. The row
that does this is not exotic: the producer publishes a one-itemcorefor a
game that completed a single item,coreItemIdsismin(2)precisely to
keep those out of an import, and they are common enough to land inside the
top four — 236 of 555 champion-roles on the 16.17 file. Amumu jungle showed
three cards with a fourth (Sunfire → Liandry’s) sitting unused directly
below the cut; Briar jungle showed three with two valid builds stranded.
Both adapters now walk the ranking and stop once four rows have passed
validation. The floor is unchanged — one-item rows are still refused, they
just no longer take a slot with them.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A build the Item Import panel refused to show cost a card slot instead of
-
released this
2026-09-03 20:30:13 -05:00 | 25 commits to main since this releaseFixed
- The T3 badge in champ select had a border you could not see. Every tier bucket draws its badge as colored text inside a matching 1px border, but T3 - the neutral middle bucket - set only the text color and left the border at the panel default
--border(#2c3540), a shade off the badge's own--panel-2background (#232b36). Next to a green T2 or an orange T4 the box read as missing rather than neutral. T3 now carries its own slate#7c8798border: visibly a box, still uncolored, so it does not compete with the buckets whose color is the signal.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The T3 badge in champ select had a border you could not see. Every tier bucket draws its badge as colored text inside a matching 1px border, but T3 - the neutral middle bucket - set only the text color and left the border at the panel default
-
v4.3.0 — Scouting report opens with a 15-request burst
StableAll checks were successfulCI / build-and-test (push) Successful in 1m29sreleased this
2026-09-01 19:54:21 -05:00 | 27 commits to main since this releaseAdded
-
The scouting report opens with a 15-request burst. Every Riot call in the
app draws on one shared token bucket at 0.65 req/s, so a run's opening rows
trickled out at roughly one every 1.5 seconds: the tenth player's rank landed
half a minute into a game that was already being played.TokenBucketRateLimiter
now takes aburstCapacity, andScoutService.run()primes it once at the top
of a run — 15 tokens at once, enough to cover rows 1-2 (10 account-v1 + 10
league-v4, minus whatever the identity cache already holds), which is the
identity and rank the report is useless without. The deep tier still finishes
at the steady cadence, which is the right place to be slow.The allowance is granted, never accrued: idle time does not build toward
it. That distinction is load-bearing, because the benchmark harvester shares
this bucket and asks for no burst — had the burst been part of the steady
capacity, a harvest resuming after a quiet stretch would have fired fifteen
unbudgeted requests at the shared key. Priming is idempotent for the same
reason a scout run can be re-triggered mid-game: a second prime tops the
balance back up to 15 rather than stacking a second burst.Budget note. 15 sits inside Riot's ~20-req/s short window, but it is 15
requests the 2-minute budget did not previously spend. The same personal key
is held by aufhocker, and the two limiters already sum over the key's 100/2min
(see the note inwireRiotClient); this widens that overrun by 15 per scout
run. It is bounded — once per run, not per player — and the client's existing
429 retry covers the case where it lands on top of aufhocker's own
game-detection burst. LowerburstCapacitythere if 429s become routine.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
released this
2026-09-01 19:30:10 -05:00 | 28 commits to main since this releaseChanged
-
The counters, synergies and builds mirrors accept
generatedFrom.previousPatch. lol-companion-data is about to publish those
three files over a pooled two-patch window, and the new field names the second
patch. It is optional and nullable here while the producer's copy is required
and nullable — that asymmetry is the whole point of the release. These schemas
are.strict(), so a producer that shipped the field first would have every
install reject the file and freeze on cached data until the app updated. The
mirror has to land first, and this is it.generatedFrom.patchis untouched. It still names the patch the file is for
and is still what the app compares against the LCU before accepting a fetch —
pooling widens what was measured, not what the file claims to describe. -
staleon a benchmarks bucket now means "the current patch has measured
nothing here yet." Benchmarks pool both patches upstream, so a bucket'sn
is the sum of the two andstaleno longer marks an either/or fallback. The
runtime already labels those buckets withgeneratedFrom.previousPatch, which
stays correct under the new meaning, so nothing about what the overlay renders
changes. The damage artifact's identically named flag does still mark a real
fallback; the two are no longer the same signal, and the schema comments now
say so.No behavioural change ships in this release. It exists so the schema change
can land before the data does.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
released this
2026-09-01 18:40:23 -05:00 | 29 commits to main since this releaseAdded
- A second champion pool, for Classic Rift. The Settings panel's "Suggested
picks" card now carries two per-lane pools: the existing one and a new
Classic Rift Champion Pool picks block with the same five lanes. Champ
select reads whichever matches the game it is in — the roster is taken from the
gameflow map id (453 = classic), the same signal the board's mode badge uses —
and a lane left empty falls back to meta exactly as before. - The two lists are deliberately not merged. Classic champions reuse their
base champion's display name but not its kit (Jade Annie has 460 HP and 550
attack range against live Annie's 560 and 625), so "the champions I play here"
is a different answer, and a name identifies a champion only within a roster.
Pool names are now resolved against the half of the champion directory the
session belongs to, so a classic pool's "Garen" resolves to 60086 rather than
quietly ranking id 86's matchups.
Changed
- The settings file gains
myClassicPool, alongsidemyPooland with the same
shape and opt-in semantics. An existingsettings.jsonneeds no edit: an
absent value means "no classic pool", which behaves as it does today. - The champion search box in each pool block no longer lists duplicate names. It
is fed by the id-keyed champion map, which carries both rosters, so every
champion with a classic counterpart appeared twice. - Editing the classic pool does not enqueue a benchmark harvest. Match v5 has no
Classic Rift games — queue 4310 results 403 permanently — so those would be
requests that cannot land. Live-pool edits pre-harvest as before.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A second champion pool, for Classic Rift. The Settings panel's "Suggested
-
v4.0.3 — lower the shared-key Riot rate to 0.65 req/s
StableAll checks were successfulCI / build-and-test (push) Successful in 1m51sreleased this
2026-08-25 20:26:34 -05:00 | 30 commits to main since this releaseChanged
- Riot client rate 0.75 → 0.65 req/s (~90 → ~78 requests per 2 min). The
0.75 was sized as though this app were the only holder of the Riot key. It is
not: aufhocker's live monitor polls the same personal key through its own
limiter, which knows nothing of this one. What Riot enforces is the sum of the
two, and at 0.75 that sum was ~165 against a 100-per-2-minutes cap — so the
bot took sustained 429s while spending only ~4% of the budget itself
(aufhocker 1.13.0 has the full account). - This narrows the overrun without closing it. aufhocker now holds a
40-per-2-min ceiling, so the pair sums to ~118 — still above 100. 0.65 was
chosen deliberately to protect harvest throughput, accepting that 429s remain
possible when both run at once. aufhocker logsmine/key/othersonce
the key-wide count passes 80% of the cap, which is where to look if they do.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Riot client rate 0.75 → 0.65 req/s (~90 → ~78 requests per 2 min). The
-
released this
2026-08-10 22:01:45 -05:00 | 31 commits to main since this releaseFixed
-
"Your Pool This Patch" was quoting an ally pairing from the wrong lane. The
champion-synergies artifact splits every pairing by the ally's role — Hwei mid
and Hwei bottom are separate rows — but the row rendered beside a pool entry
reads "50.0% with Hwei · 62 games" and says nothing about which lane Hwei
played. The consumer matched on champion id alone, so whichever single lane
happened to clear the 50-game floor was shown beside a teammate hovering that
champion in a different lane. In a real champ select, Briar's Hwei row was a
62-game mid-Hwei sample displayed next to a bot-lane Hwei; the pairing that
actually applied was 27 games at 29.6%.The indexer now sums each ally's rows across the roles it was played at and
applies the floor to the total, so a row means what its label says: games with
that champion anywhere on the team. Summing is exact rather than an
approximation — the artifact carrieswinsper row, so the combined rate is
Σwins / Σgames, not an average of rates.Splitting by ally lane was also the wrong statistical call for this section.
The same file carries no measurable pairing effect at any sample floor (see
src/shared/champion-synergies/schema.ts), so subdividing a pairing by lane
subdivides noise, and the thin slices read louder: Nocturne beside a bottom
Hwei is 66.7% over 18 games, while Nocturne beside Hwei anywhere is 59.1% over
71. Against the patch-16.15 file (26,698 matches, 119,760 pair rows) the change
lifts qualifying pairings from 3,980 to 5,331 — about a third more evidence at
the same floor, not a lowered bar.
Changed
SynergyAllyRowno longer carriesallyRole, andindexChampionSynergies
emits one row per ally champion rather than per (champion, role). This also
retires a latent duplicate-React-key case inPoolStrengthSection, which keys
ally lines on the champion id.MAX_ALLY_EVIDENCE_PER_ENTRYstays at 2, and is now what usually limits the
rows on screen rather than the game floor: a full team of hovering allies
routinely puts three or four pairings over the floor, and the two best-sampled
win. A qualifying pairing can be computed and not shown — intended, and now
documented at the constant.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
released this
2026-08-09 00:01:41 -05:00 | 33 commits to main since this releaseChanged
-
The test suite runs in about a third of the wall clock, with no coverage
given up. Two independent causes, both pure waste:-
Renderer interaction tests now share a
setupUser()helper that passes
delay: nulltouserEvent. The defaultdelay: 0awaits a real macrotask
between every sub-event of an interaction (pointerover → pointerdown →
mousedown → focus → pointerup → mouseup → click), so an interaction test's
cost was set by timer scheduling rather than by the component. The same
events fire in the same order with the same assertions available between
them; only the waiting is gone. In isolationsettings-panel.test.tsxwent
from 4191 ms to 937 ms. -
Only 21 of the 86 unit test files touch the DOM, but all of them ran under
jsdom.test/unit/renderer/**is now its ownrendererproject and the
other 65 run innode. That cost was invisible in the per-file durations
Vitest prints — about 1 ms there — because it lands in the run summary's
environmenttotal, which was 510 s on CI.
Locally the full suite went from ~45 s to ~17 s, and
environmentfrom 264 s
to 57 s.npm run test:unitruns both halves, so the split is invisible to
the scripts and to CI. All 1054 tests still pass. -
Fixed
- Two Vitest projects were handed the same
defineobject reference. Vitest
mutates it in place while resolving a project, so the second clobbered the
first and__APP_VERSION__survived as a bare identifier — every test that
rendersAppdied onReferenceError. Each project now spreads its own copy.
No user-facing effect; recorded because the failure is remote from its cause.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
released this
2026-08-08 22:54:17 -05:00 | 34 commits to main since this releaseChanged
-
The op.gg scrapers are gone, and with them every fallback in the data
layer. Counters, tier badges and rune pages each used to consult the
published artifact first and then scrape op.gg live for anything it could not
answer. That second path is deleted. Every provider now reads one published
artifact, and a question it cannot answer is reported as unanswered rather
than filled in from somewhere else.The reasoning is that the two paths were never the same answer. The crawler
fetches those op.gg pages overnight, at its own pace, under fixed filters; a
mid-draft scrape fetched a different page at a different moment. A champ
select could be served by both, and nothing on screen said which — so a
defensive-ban suggestion, a tier badge, or the rune page the Import button
wrote could depend on which source happened to answer first.What you will notice: on patch-drop morning the board shows no counters and
no tier badges until the crawler publishes for the new patch. Those
artifacts refuse an off-patch copy on purpose — a tier list is a claim about
this patch's meta — and the scrape that had already rolled over is no longer
there to cover the gap. A blank badge is visibly blank; last patch's meta
rendered as this patch's is not.Caches are unaffected and are not fallbacks under this rule: every provider
still serves its own last good copy when a fetch fails, and damage splits and
overlay benchmarks still serve an off-patch copy, because a champion's kit
survives a patch that did not touch it.
Removed
Settings.statsSourceandSettings.rateLimitPerSecond. One selected between
op.gg adapters and the other paced their page fetches; with no scraper there
is nothing to select and nothing to pace. Both keys are ignored if present in
an existingsettings.jsonand dropped the next time it is written — no
action needed. Riot API traffic (the scouting report and the benchmark
harvest) keeps its own rate limiter at 0.75 req/s.- The tier-list disk cache (
tier-list-cache.jsonunder%APPDATA%). It held
scraped pages; the counters artifact keeps its own cache, and two files with
independent TTLs disagreeing about which was stale is a bug waiting to happen.
The stale file is simply left unread and can be deleted.
Fixed
CountersArtifactAdapter.fetchChampionStatsthrew synchronously out of a
method typed to return a promise, becausePromise.resolve(lookup(req))
evaluates the lookup first. Every caller happened to sit inside atry, so it
never surfaced — but a failure that arrives as a throw rather than a rejection
is a trap for the next one.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v3.19.0 - Your Pool This Patch
StableAll checks were successfulCI / build-and-test (push) Successful in 2m6sreleased this
2026-08-08 14:06:05 -05:00 | 37 commits to main since this releaseNeeds a published-data API key. This section reads the same key as every other artifact — paste it in Settings if you have not already, or it stays on the no-data placeholder.
Added
-
"Your Pool This Patch" — a third pick list, and the only one that can be on
screen while you are still deciding. Suggested Picks cannot compute anything
until the enemy laner locks, because Riot never exposes enemy hovers; that is
most of champ select, and the section spends it showing "waiting for the enemy
laner". The new list needs only your assigned role, so it fills that window.It ranks your configured pool by each champion's own measured win rate at your
role this patch, from thechampion-synergies.jsonartifact — Riot match data,
a genuinely independent second opinion from the op.gg tier badge shown beside
it. Every row carries the games behind it, and the section names the sample it
came from (NA solo queue, Gold-Emerald), because those are one ladder's numbers
and you may not be on it.Ranking is by a shrunk win rate — pulled toward 50% by
games / (games + 400)
— so 53% over 900 games outranks 56% over 60. The displayed rate stays raw; only
the ordering is shrunk. -
Ally pairings are shown as evidence, and deliberately do not rank anything.
When a teammate hovers or locks, a row can carry a line like54.7% with Thresh · 192 gamesfor pairs with at least 50 games behind them.That restraint is the main design decision in this release, and it is measured
rather than stylistic. The artifact's headline content is 100k+ ally pairing
rows, and the obvious feature ranks picks by them. Against the published patch
16.15 file, each pair's win rate was compared to an additive baseline (the
champion's own rate plus the ally's own rate minus 50) and the spread of those
residuals compared to the binomial noise expected if pairing did not matter at
all — with the null corrected for a pair's games being a subset of both
champions' totals, which otherwise widens the noise band and manufactures a
signal:Games floor Pairs Observed SD Noise-only SD ≥ 20 9,948 8.65 8.65 ≥ 50 1,828 5.76 5.82 ≥ 100 200 3.80 4.23 Zero excess variance at every floor; a 3-point pairing effect would have shown
as 5.2 in the last row. The consequence is not that the ranking needed better
tuning — a shrinkage estimator fitted to this data shrinks every pair back to
its baseline, so the optimal ranker is the one that ignores the rows. Ranking
on them would have sorted champ-select advice by coin flips, and the top of
that list is visibly nonsense: a 35-game Lucian+Amumu pair at 77%. Even the ten
best-sampled pairs in 9.5 MB — Lucian+Nami among them — have confidence
intervals of ±5 to ±7 points around rates 1-4 points above baseline.So the pair line is a measurement with its sample attached, phrased
symmetrically ("with Thresh", never "thanks to Thresh" — both champions won or
lost the same games), and it never touches the sort. Live against production:
Syndra shows the best pairings in a five-champion mid pool and ranks fourth,
because its own record is 48.4%.This is a statement about sample size, not about the game. The producer samples
~21k matches per patch and does not carry them across a rollover; if that rises
roughly 5-7x, re-run the residual test before assuming the finding holds. -
champion-synergies.jsonis now consumed — eight of the ten published
artifacts have a consumer here. It is much the largest at 9.5 MB, which the
existing machinery absorbed better than expected: 450 KB on the wire (the API
serves it brotli), ~1 ms to validate under the sampled posture where a strict
whole-file parse of 103k rows would cost ~125 ms, and 9.2 MB retained — pruned
to 0.3 MB, since the section reads about 2% of the pair rows.It refuses an off-patch copy, matching counters and opposite to damage and
benchmarks: last patch's win rates are not a degraded answer to "how is this
champion doing now", they answer a different question. On the morning of a
patch, before the crawler has caught up, the section goes quiet rather than
showing yesterday's meta as today's.Entries below 50 games are dropped outright rather than kept and filtered at
the call site — the artifact publishes everything down togames: 1, so that
judgement had to be made somewhere, and a caller must not be able to rank on a
two-game record by forgetting a check.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-