Changelog & Roadmap

What we have

Upcoming

  1. Restrict edits so people can only change their own team, unless they're an admin. Likely needs some form of login (OAuth or similar) — low priority for now since this is an internal tool and everyone can be trusted to only touch their own team.
  2. Port the damage calculator and team prep sheet onto the website. They currently live on a separate site and already improve on Smogon's calc by pulling directly from the sheets (cached Pokémon/moves/items so you don't need six tabs open, in-progress sets saved mid-battle, etc.) while still using Smogon's own damage calc logic underneath, which shouldn't need to change.
  3. Support drafting directly on the site.
  4. Decide where backups get shipped off the VM to (git push, object storage, email, etc.) and automate it — right now backup_data only produces a local zip.

Changelog

Sep 7, 2026
Fixed the missing Urshifu sprite on the Statistics page. Showdown's species string for Single Strike Urshifu is just Urshifu — only Rapid Strike carries a style suffix — but the draft board costs the two styles as separate entries, so a replay-parsed Urshifu matched no board row and fell through to no sprite and a 0-point cost. Bare Urshifu now resolves to Urshifu-Single-Strike, so it shows its sprite, its full name, and a real kills-per-point. Name lookups also check a season's own draft board before applying these bare-species rewrites, which fixes Season 2's Zygarde the same way (S2 costs it as Zygarde, not Zygarde-50%).
Separately, backfilled 43 s3_pokedex rows that had a blank sprite_id and so rendered with no sprite at all — Urshifu, Landorus, Thundurus, Enamorus, the three Paldean Tauros forms, Basculegion/Indeedee/Oinkologne's female forms, both Calyrex riders, and their -Tera variants. S3 was populated by a one-off script rather than a checked-in loader (see the Aug 27 entry), which is where the gap came from; every backfilled sprite was checked against Showdown before being written.
Sep 7, 2026
Added a Requests page: a form at the top for submitting a feature idea or bug report (title, description, and whether it's a bugfix or a feature), with every submission listed below it. Requests are stored in the database and manageable from the Django admin. Submissions are currently credited to a placeholder author; that hooks up to the real user once login lands (see Upcoming #1).
Sep 3, 2026
Split the roster endpoint in two: /api/teams/ lists every coach name paired with their team name, and /api/roster/?coach=... returns that one coach's full drafted roster. Replaces the combined /api/rosters/ endpoint added earlier the same day, which returned every team's entire roster in one payload — far more than most callers wanted just to look up who's on a given team. An unknown coach returns a 404; a missing coach parameter returns a 400.
Sep 3, 2026
Added /api/upcoming-games/, exposing just the not-yet-played matches that have a proposed day, so callers that only care about what's coming up don't have to pull and filter the full schedule.
Sep 3, 2026
Added /api/schedule/, the first public JSON endpoint — a season's full schedule grouped by week, including replay links, winners, margins, and per-match stats. Read-only and unauthenticated, served straight from the same data_access calls the Schedule page uses, so there's no second copy of the logic to keep in sync. Takes an optional ?season= (1–4, defaulting to 4); anything else returns a 400. Documented, with example requests and field notes, in docs/schedule_api.md.
Sep 1, 2026
Replaced the six stained-glass color schemes with the seven Olympus command-center themes (quiet bronze night/day, obsidian crimson, cool slate, soft parchment, eros blush, cupid velvet), and brought Sunset back as an eighth option. Header, tabs, and controls were restyled to match — quieter chrome, theme fonts/radius, and a soft atmosphere wash instead of the old patterned backgrounds (Sunset still keeps its sunburst).
Aug 31, 2026
Schedule match stats now always list the 6 Pokémon each player brought, taken from the replay's team preview at match start. Previously only Pokémon that actually switched in were shown, so a game that ended 4–2 (or with unused bench mons) looked like a 4-Pokémon team. Unused brought Pokémon still appear, muted, with empty battle stats, and they don't count as a game played on the Statistics page.
Aug 31, 2026
Added a Standings page for every league (Season 1–4 via the League selector). Teams are ranked by wins, then head-to-head among teams tied on wins, then kill differential, then fewest losses. Unplayed matches and playoff weeks are left out of the table.
Aug 29, 2026
Dropped every raw-HP damage/healing field from the stat tracker — damage dealt/taken, residual damage by source (poison, burn, weather, Leech Seed, Salt Cure, Curse, Nightmare, binding moves, item/ability), self-inflicted damage (recoil, Life Orb, Substitute/Belly Drum cost, crash damage, confusion), and healing received are now stored and displayed purely as % of max HP, with no flat-HP counterpart. Also dropped the max_hp field itself. Reason: replays never reveal a Pokémon's real max HP (EVs are only visible to the creator), so a flat HP number was never actually comparable across Pokémon or trustworthy on its own — % of the HP bar at the moment of the event is the only value that means anything. The Statistics, All-Time Stats, and Schedule match view tables now show "Dmg Dealt %", "Dmg Taken %", and "Healing Received %" instead of flat numbers.
Aug 27, 2026
Imported real Season 2 data into the new s2_* tables, sourced from the S2 LBPP sheet: s2_pokedex (1,182 Pokémon with S2's own point costs — the per-season draft board headers now have real data behind them, not just the schema), s2_rosters (15 coaches' teams), s2_schedule (75 regular-season matches across 11 weeks, playoffs excluded), and s2_free_agency_log (21 transactions). Backed by real files under home/data/s2/ plus dedicated load_s2_* management commands mirroring the S4 loaders, already run against the database. Per-match battle stats weren't pulled from the season's replay links — that's a bigger follow-up. Two quirks carried over verbatim from the sheet rather than "corrected": Harnoor's roster sums to 121 points despite the 120-point cap, and Ajay's team name reads "HydreiGolden State Warriors," apparently a mid-season rename the sheet never fully cleaned up. Like the schema-only work before it, none of this is wired into data_access.py, the views, or the League selector yet — the app still only reads Season 4 data.
Aug 27, 2026
Populated the s3_* tables with real Season 3 data from the S3 LBPP sheet: s3_pokedex (1,578 rows — 990 draftable Pokémon across 24 point tiers, plus 588 banned/zero-point entries — the draft board's point-tier headers for S3), s3_rosters (12 teams, 10 picks each), s3_schedule (66 regular-season matches across 11 weeks, Playoff Bracket excluded), and s3_free_agency_log (17 transactions), all cross-checked against the sheet's own standings and free-agent-used counts. Unlike S2, this ran as a one-off script straight against the database rather than checked-in loader commands, so there's no home/data/s3/ JSON or load_s3_* management command in the repo backing it yet. Same caveats as S2 apply: no per-match replay stats, and nothing in the app reads S3 data yet.
Aug 27, 2026
Reworked the Schedule page layout: dropped the "pick a week, then a match" subheading, moved the week selector from a sidebar into a horizontal bar above the match list (now three stacked panes — weeks, matches, matchup), and removed the Self-KO column from the per-Pokémon stats table.
Aug 27, 2026
Laid the SQL groundwork for per-season data: refactored the s4_pokedex/s4_rosters/s4_schedule/s4_free_agency_log models into shared abstract base classes, then added matching s1/s2/s3 tables for each. Schema only for now — no historical season data has been loaded into them, and nothing in the app switches based on the League selector yet.
Aug 27, 2026
Replaced the theme swatch buttons in the nav bar with a proper Theme dropdown, and added a League selector (Season 1–4, defaulting to Season 4) next to it that remembers your choice via localStorage. The selector doesn't switch any data yet — only Season 4's data exists right now — it's just the picker.
Aug 27, 2026
Rosters now sort each team's Pokémon by points, highest first, instead of draft order.
Aug 27, 2026
Wired the new s4_pokedex table into the live app: data_access.py now loads it into an in-memory dict once at import instead of re-reading draft_board.json/sprites.json on every call. Draft board, sprite URLs, name canonicalization, free agents, statistics, and free agency point-costing/validation all read from that cached dict now.
Aug 27, 2026
Added a s4_pokedex SQL table (Django model + migration) combining draft_board.json, sprites.json, and pokedex.json — points, sprite id, types, base stats, tier, etc. for every Pokémon on the draft board (1,068 rows), loaded via a new load_s4_pokedex management command. Also added s4_rosters, s4_schedule, and s4_free_agency_log tables mirroring the other three JSON data files, each with its own loader command — first step of migrating storage off JSON files.
Aug 27, 2026
Overhauled the battle stat tracker — the replay parser went from ~10 tracked stats per Pokémon to about 90, rebuilt against a real fetched replay log rather than protocol docs alone so the attribution logic is verified against actual Showdown lines. Now covers:
  • Kills & deaths — split into direct vs. indirect kills, with fainting-attribution carried across switches. Self-KO moves (Explosion, Self-Destruct, Misty Explosion, Final Gambit, Memento) still count their damage but no longer award the user a kill.
  • Damage splits — direct, hazard, residual, contact-punishment, delayed, and self-inflicted, each tracked as % of the target's max HP (computed per hit, since max HP is only known at the moment of the event).
  • Residual damage attribution — poison, toxic poison, and burn; Sandstorm/Hail credited to the weather setter (including ability-set weather like Sand Stream); Leech Seed, Salt Cure, Curse, Nightmare, and all seven binding moves (Bind, Clamp, Fire Spin, Infestation, Magma Storm, Sand Tomb, Whirlpool, Wrap) credited to whoever caused them — even after they've switched out.
  • Healing received — by source: move healing, Wish (credited to the original wisher via Showdown's [wisher] tag, not whoever's standing in the slot when it resolves), Leech Seed drain, item/ability healing (Leftovers, etc.), and Grassy Terrain.
  • Statuses — inflicted and received, broken out by type (poison, toxic, burn, sleep, paralysis, freeze). Rest's self-inflicted sleep is deliberately excluded from both sides so it doesn't pollute a mon's status stats.
  • Entry hazards — Stealth Rock, Spikes, Toxic Spikes, Sticky Web, G-Max Steelsurge. Set and removal counts, hazard damage credited back to the setter, Toxic Spikes poison (and its residual ticks) credited to the setter, and Court Change swaps hazard ownership between sides while preserving the original setter's credit.
  • Self-inflicted/punishment damage — recoil (including Struggle), Life Orb, Substitute and Belly Drum's HP cost, High Jump Kick/Jump Kick crash damage, confusion self-hits, and contact-punishment items/abilities (Rocky Helmet, Rough Skin, etc.) credited to the defending Pokémon.
  • Delayed attacks — Future Sight and Doom Desire credited to the original user even after they've switched out or fainted before the hit lands.
  • Hit accounting — multi-hit moves count each individual hit; secondary-effect-only successes (status, boosts, hazards, weather) count a move as "landed" even with zero damage; misses, dodges, failed moves, and immunity are tracked separately from hits.
  • Crits & type effectiveness — crits dealt and taken, resisted hits taken, super-effective hits taken.
  • Stat changes — boosts and drops given, received, and self-applied (so a mon's own Bulk Up or Draco Meteor recoil doesn't get miscredited to an opponent).
  • Activity tracking — turns active, switch-ins, forced switches (Roar/Whirlwind/etc.) credited to whoever caused them, and times dragged in.
  • Identity handling — team-preview draft position, Terastallization (type + flag), and Ogerpon/Terapagos's battle-only "-Tera" form names normalized back to their draft board entry instead of forking into a separate stat line.
  • Revealed info — items and abilities revealed during the battle, tracked per Pokémon.
Attribution stays conservative throughout: where the log doesn't expose a reliable source, the event is recorded as taken/received but left unattributed rather than guessed at.
Aug 27, 2026
Fixed a bug found while validating the tracker rewrite: the HP-delta helper always reported HP lost, so every -heal line was silently registering as zero healing — Wish, Leech Seed, Roost, drain moves, Leftovers, all of it. Fixed by flipping the sign specifically for the healing path.
Aug 27, 2026
Re-parsed all 10 already-submitted replays under the new stat tracker schema so existing matches show the richer stats immediately, rather than sitting at zero until someone resubmits them. Verified end-to-end in the browser: the Schedule match view and Statistics page both render correctly with real data, zero console errors, and zero unattributed damage leaks across a full test replay.
Aug 27, 2026
Added new columns to the Schedule match view and Statistics page: direct/indirect kills, self-KOs, healing received, crits dealt/taken, boosts given/received, hazards removed, and a Tera badge next to Terastallized Pokémon's name. Not every tracked field got a column — that would be unusable — the rest of the ~90 fields are stored per match in the data layer for future use (deeper per-Pokémon breakdowns, a match summary view, etc.).
Aug 27, 2026
Added a data backup management command (backup_data) that zips the JSON data files into a timestamped archive.
Aug 27, 2026
Giovanni posted a trade to Discord: Bayern Munchlax dropped Chi-Yu, Slither Wing, and Ditto to pick up Darkrai, Victini, and Lapras.
Aug 27, 2026
Created Giovanni, a Discord webhook bot that posts free agency/trade transactions to the league Discord.