-
released this
2026-07-29 18:17:03 -05:00 | 59 commits to main since this releaseFixed
-
Champion display names stopped being unique, and it silently cost 35% of the
roster its stats. Patch 16.15 shipped the Classic Rift mode (map 453, queue
4300) and with it 60 alternate champion entries in the client's
champion-summary.json— aliasJade_<Base>, id60000 + base id. Every one
carries its base champion's display name, so "Garen" now names both id 86 and
id 60086.ChampionNameResolverbuilt its name→id maps last-write-wins, and the asset is
listed id-ascending, so the classic id always landed last and won. All 60 shared
names resolved to a60xxxid — Ahri, Annie, Garen, Lux, Teemo, Wukong, Master
Yi, Miss Fortune, Nunu & Willump and 51 more. Nothing in the published artifacts
is keyed by those ids, so the in-game overlay, the scouting report, and both
op.gg adapters quietly lost benchmarks, damage splits, and counters for a third
of the roster, with no error surfaced.Champ select was never affected: it passes numeric
championIdstraight
through, and ids stayed unique throughout. Only name→id resolution was
ambiguous.
Changed
-
Name→id resolution now takes the roster it means, because breaking the tie
the obvious way would have been worse. The Live Client Data API identifies
participants by display name, so simply preferring the lowest id would have
fixed normal games and started answering classic ones with live-roster stats.
Jade Annie has 460 HP and 550 attack range against live Annie's 560 and 625, on
a reworked kit — that is a confident wrong number where an unresolved id
honestly renders "—", and showing nothing is the recoverable failure.So the lookups are split per roster and the in-game poller identifies the game
before asking.rawChampionNameis the primary signal, being per-participant
and exact;mapNameis the fallback.gameModeis deliberately ignored —
ordinary Summoner's Rift reports the literal string"CLASSIC", so it
identifies the wrong thing. Anything unidentified stayslive, which is both
the safe default and the overwhelmingly common case, and the verdict is logged
once per game (live.roster_detected) with the raw strings beside it so a real
classic game confirms themapNamespelling.op.gg adapters stay pinned to the live roster: op.gg publishes no classic pages,
so a classic subject 404s to "no data" until it does.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v3.9.1 — scouting report: team damage rollup only
StableAll checks were successfulCI / build-and-test (push) Successful in 2m18sreleased this
2026-07-28 22:39:20 -05:00 | 61 commits to main since this releaseChanged
-
The scouting report shows the team damage rollup only — the per-champion bar
is gone. 3.9.1 reverses 3.9.0 on this point. That release dropped the bars
from the champ-select board but deliberately kept them on the report, on the
reasoning that "one player is the point" there. In use that turned out to be
wrong: a champion's damage split is a property of its kit, so the bar reads the
same on the card as it does folded into the column rollup above it, and the
report is the surface where that costs most — it is read mid-game under time
pressure, and ten repetitions of a fixed number were pushing apart the rows
that genuinely differ per player (rank, mastery, on-champ form).Both surfaces now follow one rule: damage is a team question, answered once per
column.ChampionDamageBarandsplitTooltipare deleted rather than left unused —
neither surface renders per-champion splits now, and the coverage they had
(thin sample, previous-patch fallback, off-patch aggregate) is asserted on
profileTooltip, which is what the rollups actually use.No mid-game refinement is possible here for now: Riot's Live Client Data API
exposes no damage figures for any player (scorescarries only kills, deaths,
assists, creep score and ward score), so the rollup stays kit-derived until the
post-game match data lands.
Fixed
- A test that passed on the day it was written and failed every day after.
stats-provider-artifact.test.tshardcoded a fixturefetchedAtwhile leaving
the provider on the real clock. The provider persists the payload's own
fetchedAtrather than a write timestamp, so once that literal aged past
cacheTtlHoursthe cached entry stopped counting as fresh and a test asserting
a cache hit began asserting against a genuine cache miss. The clock is now
pinned to the fixture. Test-only — the provider's lookup order was never wrong.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
released this
2026-07-27 20:51:18 -05:00 | 62 commits to main since this releaseChanged
-
Rune pages now come from the nightly crawler instead of being scraped during
champ select. Recommended runes were the last thing this app fetched from
op.gg at the moment it could least afford to: lock-in gives you about thirty
seconds, the request queued behind stats and tier-list fetches on the shared
rate limiter, and the result was cached in memory only — so every restart
re-scraped every champion you had looked at.lol-companion-data now crawls those same pages nightly, off the same
tier-list index it already fetches for counters, and publishes
data/champion-runes.json. Lock-in reads it out of memory.op.gg stays wired as the fallback and nothing about the feature changes when
it is used. A miss for any reason — the artifact not fetched yet, a patch
mismatch, a champion op.gg does not list at that role — falls straight through
to the scraper, exactly as before. The artifact is refused off-patch
(servesOffPatch: false), because a recommended build is a claim about
this patch's meta and the crawler is a night behind on patch-drop morning.The published file stores board rows as bare perk ids against a shared perk
dictionary rather than repeating names and icon URLs on every option: measured
over the same data, 212 KB against 1.9 MB. Which rune each row picked is
derived positionally fromselectedPerkIds— deliberately not by testing
membership, because stat shard ids repeat across rows (5008 in rows 1 and 2,
5001 in rows 2 and 3) and a membership test would highlight two shards where
the page picked one.Perk hover descriptions are unaffected. They come from the running client's
perk catalog over the LCU, which a headless crawler has no access to, so they
are still attached on this side for both paths. -
The champ-select board no longer tells you what to build. Each team's
damage rollup used to end in an instruction — "Mostly AP → build MR". The same
rollup renders for both columns, and that advice is only ever right about one
of them: under YOUR team it read as "your team is AP-heavy, so build MR",
which is backwards. The headline now names the composition and stops there
("Mostly AP"), which is true of whichever column you are reading. The scouting
report's rollups carried the same line and lost it for the same reason. -
Enemy lane dropdowns state the lane outright. They used to be prefixed
with their provenance — "Guess · Jungle", "Auto · Support" — which spent the
widest label in the row on a qualifier, clipped under the dropdown arrow at
the longest lanes, and told the reader something the row's position already
told them. The dropdown now reads "Jungle". The lane it defaults to is also
dropped from the list below it, so the open menu no longer offers the same
lane twice. -
"Support" is now "Utility" on the board, matching the role name the ally
column has always shown. The off-role tier tag follows (SUP→UTIL). Other
surfaces are untouched: the settings panel and the rune/item import headings
still say Support, and imported item-set names (LoLC: Lux Support) keep
theirs so already-imported sets are not orphaned in the client.
Removed
- Per-champion damage bars on the champ-select board. Ten of them, one per
cell, restated in ten thin bars what the two team rollups above them already
say in words — and the team number is the one a player actually itemises
against. The rollups stay. The per-player bar is unchanged on the scouting
report, where a single player's profile is the point of the surface.
Fixed
- The two team columns line up row for row again. The ally column renders
its role as plain text and the enemy column as a dropdown, which is several
pixels taller, so the two sides drifted further apart with every row — by the
middle lane they were visibly offset, in a layout whose whole purpose is
reading TOP against TOP. Both sides now reserve the same height for that line,
and a cell with no tier badge (an unfilled lane, or a champion op.gg has no
data for) holds its badge row open instead of collapsing it, so the columns
stay aligned mid-draft as well.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
released this
2026-07-27 16:47:36 -05:00 | 64 commits to main since this releaseChanged
The overlay now compares against the nearest measured tier instead of nothing.
Every source of benchmark numbers is banded well inside the ladder: the nightly crawler publishes GOLD/PLATINUM/EMERALD, and the seed shipped in the app is GOLD-only. An exact-tier lookup therefore answered nothing at all for an Iron, Bronze, Silver, Diamond, Master, Grandmaster or Challenger player — the CS/min and gold/min comparison lines simply stayed blank until their own harvest slowly filled a bucket in, which is exactly the Riot budget this app exists to stop spending.
A miss now walks outward along the ladder and takes the closest tier any layer does have: Bronze and below compare against GOLD, Diamond and above against EMERALD. Every seeded champion has a comparison at every rank from the first launch.
The substitution is reported, not hidden — the overlay captions itself with the tier that actually answered ("vs GOLD avg"), never with the tier it asked for. Showing Gold numbers under a Bronze label would misreport what you are being measured against, which is worse than showing nothing.
Exact hits are untouched: your own tier is tried across all three layers before any neighbour is considered, so a real Bronze bucket from your harvest still beats the artifact's Gold one. Equidistant ties resolve downward. Nothing hardcodes the band, so if the crawler's tier range is ever widened, real buckets start winning on their own.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v3.7.0 — counters, tier lists and benchmarks from the nightly crawler
StableAll checks were successfulCI / build-and-test (push) Successful in 1m47sreleased this
2026-07-27 13:52:01 -05:00 | 65 commits to main since this releaseCounters, tier lists and the overlay's benchmark targets now come from the nightly crawler in lol-companion-data instead of being re-fetched from op.gg while you are in draft.
Counters and tier lists
The crawler already fetches every op.gg counters page and all five tier-list pages each night — it has to, since op.gg has no endpoint that enumerates which champions are played at which role. It now publishes them, and the app reads that one file.
A champ select used to cost one op.gg page fetch per enemy champion, queued through the shared rate limiter, plus five more per patch for the tier badges. Those are now map lookups against a file fetched once on connect.
The scraper is still there and still correct. Anything the artifact does not cover — a champion op.gg does not list at that role, a patch the crawler has not caught up to, an unreachable Forgejo — falls straight through to it, with no retry delay in between. On patch-drop morning the crawler is a night behind, and an off-patch tier list is a claim about the wrong meta rather than a slightly old one; the app recognises that and scrapes for the day instead.
Both sources score a matchup through the same module, so a defensive-ban suggestion cannot depend on which one answered. The artifact publishes only what op.gg reported — games, wins, win rate — and never a counter rating: that number is tuned against this app's threat threshold and belongs on this side.
Overlay benchmarks
The in-game CS/min and gold comparisons had two sources: your own harvest, which spends your Riot key competing with the scouting report for the same budget, and a
benchmarks.jsoncommitted to the repo that only moves when a release ships one. The published artifact is a third, covering 173 champions across Gold/Platinum/Emerald and refreshing nightly for free.Lookups now resolve per (champion, role, tier) rather than per file. A harvested
benchmarks.jsonused to replace the bundled seed outright, which was backwards — the harvest fills in one champion at a time as you play, so for weeks a returning player had fewer comparisons than a fresh install.
Install:
LoL-Companion-3.7.0-setup.exe(installer) orLoL-Companion-3.7.0-portable.exe(no install).Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
released this
2026-07-26 22:41:22 -05:00 | 66 commits to main since this releaseAdded
Champion damage-type splits on the board and the scouting report. Every drafted champion now carries a physical / magic / true bar, and each team carries a weighted rollup that names the call — "Mostly AD → build armor".
The numbers come from the nightly crawler in lol-companion-data, which has been publishing them for a while with nothing on this side reading them. The app now fetches the published aggregate at runtime, strict-parses it against a mirrored copy of the producer schema, and caches the last good copy under
userDataso it survives a failed fetch, an offline session, or a cold start. No Riot key and no League client are needed — the splits are ready before the client is even up.The rollup weights each champion by its role share of team damage, using the weights the artifact ships. A flat mean would count a support magic split as heavily as the ADC, and the resulting armor-vs-MR call would be wrong for exactly the players relying on it. Both teams are rolled up separately on both surfaces, and a rollup covering only part of a team says so rather than implying full coverage.
Per-champion splits are read at the lane the champion is shown in, so re-assigning an enemy lane on the board re-reads their split for that lane. A role bucket is only preferred over the champion overall average once it has enough games behind it, since the producer deliberately publishes buckets as thin as a single game; a thin split is marked with
*rather than hidden, and a champion the crawler has never seen shows no bar at all rather than a zeroed one.A patch mismatch is treated as expected, not as an error: the crawler needs a night of games on a new patch before it can publish for it, so on patch-drop day the previous patch aggregate is shown and labelled rather than blanked.
Notes
- Read-only against the League Client (LCU) — never writes to the client.
- The in-game overlays on-screen render has not yet been verified in a live match.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
released this
2026-07-26 17:21:50 -05:00 | 67 commits to main since this releaseChanged
- Rune Import now shows at most four combos. The list emitted one row per distinct primary+secondary tree pair op.gg reports, with no cap — so a champion that runs two keystones could push past the intended four. Briar jungle listed five: Press the Attack in three pairings, Hail of Blades in two, the fifth a 161-game page whose 64% win rate is precisely the low-sample-high-winrate trap the importer is supposed to avoid. The list is now capped at the top four by total games, matching the item importer's existing
MAX_ITEM_OPTIONSand the two-per-row board grid. The cap trims combos only — every build still rolls into its combo's stats and the pick-rate denominator is unchanged, so the four surviving rows read exactly as before.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Rune Import now shows at most four combos. The list emitted one row per distinct primary+secondary tree pair op.gg reports, with no cap — so a champion that runs two keystones could push past the intended four. Briar jungle listed five: Press the Attack in three pairings, Hail of Blades in two, the fifth a 161-game page whose 64% win rate is precisely the low-sample-high-winrate trap the importer is supposed to avoid. The list is now capped at the top four by total games, matching the item importer's existing
-
v3.5.7 — renderer lifecycle logging + dead-renderer recovery
StableAll checks were successfulCI / build-and-test (push) Successful in 1m40sreleased this
2026-07-23 19:19:16 -05:00 | 68 commits to main since this releaseFixed
- A dead renderer is now reloaded instead of leaving a permanently blank window. If a window's renderer process exits abnormally nothing repaints it, and for the transparent in-game overlay that is indistinguishable from "no game running". Renderers now reload automatically on
render-process-gone, capped at 3 attempts.
Added
- Renderer lifecycle logging for all three windows (companion, overlay, scout):
window.loaded,window.load_failed,window.preload_error,window.console_error,window.renderer_gone,window.renderer_reloaded. Renderer-side failures previously never reached the log at all. overlay.shown/overlay.hidden— visibility flips right after the first successful live-metrics publish, separating "the poller never got data" from "data arrived but the panel painted nothing".
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A dead renderer is now reloaded instead of leaving a permanently blank window. If a window's renderer process exits abnormally nothing repaints it, and for the transparent in-game overlay that is indistinguishable from "no game running". Renderers now reload automatically on
-
released this
2026-07-21 18:40:41 -05:00 | 69 commits to main since this releaseFixed
- Existing installs now actually get the 24h counter-data TTL instead of staying frozen for a week. v3.5.4 lowered the default
cacheTtlHoursfrom 168 (1 week) to 24, but only for fresh installs — it shipped no migration, so anyone who had used the app before kept the old 168h value persisted in theirsettings.json. That TTL governs healthy (non-thin) matchup data, so the "Defensive Bans" counters kept serving snapshots up to a week old. Because op.gg's per-patch game counts accumulate daily, a multi-day-old cached entry showed far fewer games than op.gg currently reports (e.g. a jungle matchup cached at ~150 games while op.gg had already reached ~360) — which read as "the game counts are still really low." Loading settings now migrates a persistedcacheTtlHoursequal to the old default (168) to the current default (24). The field has no in-app control, so a stored 168 can only be the inherited old default: only that sentinel is rewritten, and a value hand-edited anywhere else in the 1–168 range is left untouched. The max configurable TTL stays 168.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Existing installs now actually get the 24h counter-data TTL instead of staying frozen for a week. v3.5.4 lowered the default
-
released this
2026-07-21 18:33:18 -05:00 | 70 commits to main since this releaseFixed
- The in-game Scouting Report no longer gets stuck showing empty team columns for the whole game. Opening the overlay during load-in — while you are at level 1 in the fountain, before Riot's Live Client Data API has populated the 10-player roster — read an empty player list and published a report with zero players, which rendered as bare "Your team / Enemy team" headers with no cards. Worse, that empty run was misread as a fully resolved one (an empty player list trivially has no unresolved identities), so it cleared the retry timers and latched the "already gathered this game" flag — meaning neither re-opening the overlay nor the live poller later seeing a full roster ever re-triggered gathering. The report now bails without publishing when the feed exposes no players yet, leaving the honest "Gathering report…" state and the retry path armed, so the next open — or the game becoming active with a populated roster — fills it in.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads