• v4.6.0 0bef2a0975

    v4.6.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m21s
    Stable

    joeyr released this 2026-09-07 22:10:37 -05:00 | 11 commits to main since this release

    Added

    • The Item Import cards now show an item's shop card on hover, not just its
      name.
      A row offering "Lich Bane -> Stormsurge" named the sequence and left
      you to remember what Stormsurge costs, what it gives and what it does. The
      tooltip now carries the price, the stat lines, and each passive or active as
      its own block:

      Stormsurge - 2,800g
      90 Ability Power
      15 Magic Penetration
      6% Move Speed
      
      Stormraider
      Dealing 25% of a champion's maximum Health within 2.5s applies Squall to them.
      

      Data Dragon ships that text as an HTML blob, so a new gen:item-details
      generator flattens it offline into src/shared/item-details/ and the app
      commits plain strings. Parsing at author time rather than in the renderer
      means no dangerouslySetInnerHTML over vendor markup and no parser in the hot
      path of a hover -- and a tag the parser does not know fails the generator
      instead of reaching a user as a literal <passive> mid-champ-select.

      The asset deliberately holds no item names: the builds artifact is the
      naming authority, so the tooltip takes the name from the option it is drawn
      for and only the detail from here. A renamed item therefore shows its current
      name beside a slightly stale stat line rather than the reverse, and an item
      the asset has never heard of degrades to exactly the old behaviour -- the bare
      name.

    Changed

    • Item costs regenerated on Data Dragon 16.17.1, two patches on from the
      16.15.1 extract the overlay's gold difference was using. Three prices had
      moved (Black Cleaver 2700 -> 2800, Devourer's Emblem 3000 -> 3200), one id had
      gone and one had appeared. Left alone, the app would have quoted two different
      prices for the same item -- the new tooltip's and the gold difference's.
    Downloads
  • v4.5.7 738752e53a

    v4.5.7
    All checks were successful
    CI / build-and-test (push) Successful in 2m13s
    Stable

    joeyr released this 2026-09-06 18:59:16 -05:00 | 14 commits to main since this release

    Fixed

    • Both in-game overlays could paint underneath League, which looked exactly
      like a broken overlay and a dead scout hotkey.
      Caught live: mid-game, the
      live-metrics overlay was nowhere on screen and Ctrl+Alt+S appeared to do
      nothing — while the log showed overlay.shown and three scout.toggle events
      firing correctly. Enumerating the windows explained it: both overlays were
      visible, click-through, and still carried the WS_EX_TOPMOST style bit, yet
      Windows had dropped them below the game window in the actual z-order (the
      live overlay sat at position 61, League's game window at 20). The style bit
      only says which band a window belongs to; it does not hold its place in that
      band, and nothing re-asserted the placement after the window was first shown.

      Both windows are now re-raised with moveTop() (a SetWindowPos(HWND_TOP, SWP_NOACTIVATE), so focus stays with the game) the moment they are shown, and
      a slow keeper re-asserts it every 3 s for as long as either is visible —
      covering the demotions that happen mid-game on alt-tab, a client popup, or a
      resolution change. Hidden and destroyed windows are skipped, so nothing runs
      outside a game.

    Downloads
  • v4.5.6 7603c49df6

    v4.5.6
    All checks were successful
    CI / build-and-test (push) Successful in 2m2s
    Stable

    joeyr released this 2026-09-06 11:05:27 -05:00 | 15 commits to main since this release

    Fixed

    • The scouting report's lane arrows and the WEAK LINK badge no longer
      contradict the card they sit on.
      Both verdicts were computed from ranked LP
      and on-champ form only, while the ROLE line right beneath them — recent win
      rate and KDA at that position, across every champion — was fetched, rendered
      and then ignored by the maths. So a 152 LP edge handed a favourable arrow to an
      ally first-timing their pick at 33% in the role, against an enemy at 44%; and
      the weak-link badge landed on the highest-LP-deficit ally rather than the one
      actually losing their lane, even when a teammate's own role line said as much.

      laneVerdict and threatScore now take a third component from roleStats,
      scored the same way as on-champ form (win rate against a coinflip, discounted
      by a squared ramp to the five-game sample floor) at 0.6 weight, because role
      form is the broader read of the two. In practice it moderates a rank-only call
      more often than it flips one — the case above now reads "even" — but it is the
      only evidence available for a player with no history on their pick, which is
      exactly when the old scoring was least defensible. The sample discount,
      isLowSample, and the two components are now written once against a shared
      SampledStats shape rather than duplicated per section.

    Changed

    • The ROLE row leads with its game count — 6g · 55% · 3.10 KDA — and dims
      itself with the same amber * the on-champ row uses when the sample is under
      five games. Both rows are cut from the same ten-match request, and the role
      line is the thinner of the two because it keeps only the games played at that
      position, so a bare 70% was routinely three games of noise presented with
      more confidence than the on-champ number beside it, which had carried a
      low-sample marker all along.
    Downloads
  • v4.5.5 f476f158b3

    v4.5.5
    All checks were successful
    CI / build-and-test (push) Successful in 1m58s
    Stable

    joeyr released this 2026-09-06 09:16:02 -05:00 | 16 commits to main since this release

    Fixed

    • Champions already locked in by another player no longer appear in "Suggested Picks" or "Your Pool This Patch". An ally locking Briar top left Briar sitting in the pool list while the user was still picking jungle — a suggestion for a champion nobody can pick. Both recommenders exclude session.locked, but that list was read only from a top-level locked field the live LCU never populates: a captured champ-select payload carries locked: [] for the entire phase, and the locked pick actually lives on the cell's own championId. Extraction now derives the list from both teams' cells (plus completed type: 'pick' actions) whenever the explicit array is empty — the same fallback the ban list has needed since bans.* turned out to be empty too. Knock-on effects: the defensive-ban list stops treating a locked ally's champion as a live hover, and the "picking has begun" fallback that reveals Suggested Picks in a ban-less lobby now actually fires.
    Downloads
  • v4.5.4 68dc882114

    v4.5.4 — Dagger marker for assumed cooldowns
    All checks were successful
    CI / build-and-test (push) Successful in 1m32s
    Stable

    joeyr released this 2026-09-05 23:13:20 -05:00 | 17 commits to main since this release

    Changed

    • The assumed-cooldown marker is a superscript dagger rather than a tilde —
      4:14†. A dagger is the conventional typographic mark for "this carries a
      caveat", which is precisely the claim being made, and it sits quieter beside a
      clock than a tilde does. It still hangs off the time rather than the champion
      name, because which champion used which spell is known exactly and only the
      duration is inferred. No legend is printed.
    Downloads
  • v4.5.3 05ec7d87fc

    v4.5.3 — Compact assumed-cooldown marker
    All checks were successful
    CI / build-and-test (push) Successful in 1m31s
    Stable

    joeyr released this 2026-09-05 22:55:10 -05:00 | 18 commits to main since this release

    Changed

    • The Cosmic Insight caveat is now a single character. The overlay used to
      mark assumed rows with a degree sign and print a ° assumes Cosmic Insight
      footnote beneath the list. On a panel that has to be read at a glance
      mid-fight, that was a whole extra line explaining a distinction most of the
      time nobody is acting on.

      The marker is now a ~ on the clock itself — ~4:14 — which reads as
      "approximately" on sight and needs no legend, so the footnote is gone
      entirely. Putting it on the time rather than the champion name also puts it
      where the uncertainty actually lives: the identity is known, the duration is
      the guess.

      Nothing about the maths changed. Durations still assume Cosmic Insight
      whenever an enemy runs an Inspiration tree, because the live API never exposes
      minor runes for other players and an early timer is the safe error.
      assumedCosmicInsight remains on the IPC message and in the logs.

    Downloads
  • v4.5.2 1deb907b76

    v4.5.2 — Chat ping parsing fixes
    All checks were successful
    CI / build-and-test (push) Successful in 1m30s
    Stable

    joeyr released this 2026-09-05 22:43:10 -05:00 | 19 commits to main since this release

    Fixed

    • Chat pings were dropped from 10:00 game time onward. Tesseract routinely
      splits a two-digit minute with a stray space, reading 13:03 as 1 3:03 —
      3 of 11 lines in one measured live capture. The timestamp pattern required the
      separator to follow the minute digits immediately, so every one of those lines
      failed to match and was discarded.

      The failure was invisible in the worst way: a dropped line is indistinguishable
      from a line that was never a ping, so nothing logged and nothing surfaced. It
      also hid behind early-game testing — a clock like 00:13 has a single minute
      digit and survives, which is exactly why the first verified pings (Vex Flash at
      0:13 and 0:20) worked while a later one did not.

      The digit groups now admit internal spaces and strip them before the value is
      read, with the seconds still required to be exactly two digits so a greedy
      match cannot run into the sender's name. Regression tests use verbatim
      tesseract output rather than idealized strings.

    • The verbose ping phrasing was dropped. League emits more than one wording,
      and both turn up in the same game one second apart:

      06:28 JRendar (Amumu): Malphite Flash
      06:28 JRendar (Amumu): Cho'Gath used Flash
      

      Everything before the spell was treated as the champion name, so the second
      line asked the roster for a champion called "Cho'Gath used" and silently
      dropped a real ping — visible on screen as one tracked Flash where two were
      called. Connective words (used, uses, has, just) are now stripped from
      the end of the champion candidate, never the start, so a champion whose name
      legitimately contains one is unaffected.

    Changed

    • OCR latency measured at ~900ms per pass against a 2s poll, so the cadence has
      room and is unchanged. Worth recording because the wrong diagnosis here was
      tempting: chat lines fade after ~10s, which makes "we polled too slowly" a
      plausible-sounding but incorrect explanation for a missed ping.
    Downloads
  • v4.5.1 abe204541f

    v4.5.1 — Multi-word summoner spell names
    All checks were successful
    CI / build-and-test (push) Successful in 1m31s
    Stable

    joeyr released this 2026-09-05 22:23:52 -05:00 | 21 commits to main since this release

    Fixed

    • Multi-word summoner spell names in chat pings. The parser assumed a spell
      name was always the payload's last single word, which is wrong twice over.
      Teleport is renamed Unleashed Teleport once it upgrades, so a ping on it
      after that point never resolved at all. And Smite's jungle forms broke the
      split the other way: a real line, 13:09 JRendar (Evelynn): Riven Unleashed Smite, parsed as a champion called "Riven Unleashed" using "Smite" rather
      than as Riven using "Unleashed Smite".

      Both halves of the payload can now be multi-word. The parser walks candidate
      spell suffixes longest-first and accepts the first split where the champion
      and the spell both resolve against their vocabularies — requiring both is
      what stops a bad split from inventing a champion that does not exist. The
      upgraded forms are listed explicitly (Unleashed Teleport at the same 240s;
      Chilling/Challenging/Unleashed/Primal Smite), so the Smite variants
      now resolve and are then cleanly rejected as charge-based instead of
      mis-splitting.

      Found by reading a live game's chat rather than a bot game's: the only spell
      ping that had occurred was an upgraded Smite, and it would have been dropped
      for the wrong reason.

    Downloads
  • v4.5.0 cd67018246

    v4.5.0 — Enemy summoner-spell cooldowns
    All checks were successful
    CI / build-and-test (push) Successful in 1m32s
    Stable

    joeyr released this 2026-09-05 21:49:40 -05:00 | 22 commits to main since this release

    Added

    • Enemy summoner-spell cooldowns in the in-game overlay, driven by the chat
      lines your team's scoreboard pings post. Holding Tab and clicking an enemy's
      summoner spell writes 02:19 Name (Champ): Pantheon Flash to team chat; the
      overlay now reads that off the HUD and counts the spell down.

      Chat is the only signal available. Riot's Live Client Data API publishes no
      summoner-cast events and no champion positions, and the Tab scoreboard shows
      no enemy cooldowns, so nothing can detect a Flash automatically — a teammate
      still has to ping it. What is automated is the duration, which is the part
      every manual tracker gets wrong.

      Three properties of the chat line carry the design. It timestamps itself
      with the game clock, so OCR latency is irrelevant — a line read eight seconds
      late still yields an exact remaining cooldown, and there is deliberately no
      freshness gate (unlike the CS/clock fusion, which has one). That timestamp
      also dedupes: the same line is re-read every poll while it sits on screen,
      and champion|spell|time is stable across those reads. And both fields come
      from closed vocabularies — five enemy champions, nine spell names — so
      every parse is validated against a known set rather than trusting raw OCR,
      which is what makes fuzzy matching safe (Panlheon Fiash resolves; noise
      does not).

    • Haste-aware durations. Two sources are folded in, and they are not equally
      knowable. Ionian Boots of Lucidity (+10) is observed — items is
      published for every player on both teams, across the base, Arena and Classic
      Rift id blocks. Cosmic Insight (+18) is assumed: the live API exposes
      only the keystone and the two tree names for other players and never the minor
      runes, so the rune is inferred whenever Inspiration appears in either tree.
      The assumption is deliberately pessimistic — a timer that expires early is
      safe, one that expires late gets you killed — and the overlay marks those rows
      with a ° and a footnote so an assumed number is never mistaken for a
      measured one. Flash therefore reads 5:00, 4:33, 4:14 or 3:54 depending on what
      they have: a 66-second spread.

      Remaining time is derived per snapshot from current haste rather than stored
      as a deadline, so an enemy who buys Ionian Boots while Flash is down has the
      running countdown shorten itself the moment the boots appear in their
      inventory — which is what the game actually does.

    Changed

    • LivePlayerSchema now carries the payload's runes and summonerSpells
      blocks (both optional, matching the schema's deliberately lenient posture).
    • The chat OCR reader thresholds on the value channel (max(R,G,B)) rather
      than the CS reader's luminance. Pure red has a luminance of 76, below the
      existing threshold of 130, so a luma pass would have erased exactly the field
      that names the champion.
    • regions.json gains a chat box, calibrated on 3440x1440. It is optional in
      the schema, so an older calibration file still loads and simply leaves the
      tracker idle.
    Downloads
  • v4.4.0 f235a7a636

    v4.4.0
    All checks were successful
    CI / build-and-test (push) Successful in 1m37s
    Stable

    joeyr released this 2026-09-05 20:36:44 -05:00 | 23 commits to main since this release

    Added

    • A "Refresh data now" button in the settings panel. Published artifacts are
      cached and only re-fetched when that copy is older than cacheTtlHours (24h)
      or describes a different patch. That check is not a launch check, so
      restarting the app does NOT re-fetch a cache that is merely a few hours old,
      and cacheTtlHours has no in-app control by design — which left deleting
      files from %APPDATA% or hand-editing settings.json as the only overrides.
      The window this closes is narrow but real: the crawler publishes once a night
      at 07:00, so the morning after, yesterday evening's cache is still inside its
      TTL and the app will not go looking.

      The button force-refreshes every artifact — benchmarks, synergies, counters
      and tiers, runes, item builds, Classic Rift items, damage splits — and reports
      what landed. force skips the within-TTL shortcut and nothing else: a forced
      fetch that fails still falls back to the cached copy, because a manual refresh
      is no reason to leave champ select with nothing. The refreshers run
      sequentially rather than in parallel, since these are up to ~9.5 MB each
      against one small API and the automatic path spreads them across a session.

      The two Classic Rift files cannot by themselves make the result look broken.
      Their producer pass is opt-in and the mode is limited-time, so a 404 is their
      normal state, and reporting that as a failure is how a status indicator gets
      ignored.

    • A champion in a pool that its roster does not have is now marked. Pool
      chips are matched against the roster's own champion list, and one with no
      match is drawn with a dashed border and a title saying it is ignored. The
      live and Classic Rift pools hold the same champion names against different
      kits, so a name carried into the classic pool that Classic Rift does not have
      was silently dropped by the pipeline while still looking configured in the
      panel. Only applied once the client has actually listed champions — the list
      is empty before the LCU connects, and striking through a whole pool on a cold
      start would be worse than saying nothing.

    Downloads