{"id":46464210,"url":"https://github.com/shantingliu/pugs-balancer-app","last_synced_at":"2026-03-13T23:14:32.780Z","repository":{"id":342455311,"uuid":"1173039691","full_name":"ShantingLiu/pugs-balancer-app","owner":"ShantingLiu","description":"Team balancer for Overwatch 2 custom games with adaptive matchmaking based on ranks, roles, and match history.","archived":false,"fork":false,"pushed_at":"2026-03-08T11:01:13.000Z","size":700,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-08T14:11:18.661Z","etag":null,"topics":["matchmaking","overwatch","overwatch2","react","tauri","team-balancer","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ShantingLiu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T00:22:00.000Z","updated_at":"2026-03-08T11:01:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"7b2c8ce9-256a-4a2f-9a82-83a62c8fc21d","html_url":"https://github.com/ShantingLiu/pugs-balancer-app","commit_stats":null,"previous_names":["shantingliu/pugs-balancer-app"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ShantingLiu/pugs-balancer-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShantingLiu%2Fpugs-balancer-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShantingLiu%2Fpugs-balancer-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShantingLiu%2Fpugs-balancer-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShantingLiu%2Fpugs-balancer-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ShantingLiu","download_url":"https://codeload.github.com/ShantingLiu/pugs-balancer-app/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShantingLiu%2Fpugs-balancer-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30297111,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T11:12:22.024Z","status":"ssl_error","status_checked_at":"2026-03-09T11:10:54.577Z","response_time":61,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["matchmaking","overwatch","overwatch2","react","tauri","team-balancer","typescript"],"created_at":"2026-03-06T04:08:46.742Z","updated_at":"2026-03-13T23:14:32.772Z","avatar_url":"https://github.com/ShantingLiu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PUGs Balancer\n\nA desktop application for balancing Overwatch 2 PUG (Pick-Up Game) teams. Automatically creates fair team compositions based on player ranks, roles, hero pools, and preferences.\n\n## Features\n\n- **Multi-Mode Support**: Switch between Stadium 5v5, Regular 5v5, and Regular 6v6 game modes\n- **Smart Team Balancing**: Uses **multi-restart simulated annealing** to find optimal team compositions — generates random valid arrangements, iteratively improves them through player/role swaps with temperature-based exploration, and repeats across multiple restarts to avoid local optima. Scales to any lobby size (10–100+ players) in milliseconds.\n- **Role-Based Composition**: Ensures proper team composition (1-2-2 for 5v5, 2-2-2 for 6v6)\n- **Archetype Parity**: Checks flyer vs hitscan coverage (Stadium mode only)\n- **One-Trick Detection**: Warns when one-trick players conflict (Stadium mode only)\n- **Mode-Aware Hero Pools**: Stadium mode uses restricted hero roster (31 heroes)\n- **Soft Constraints**: Prefer certain players together or apart\n- **Loss Streak Compensation**: Favors players on losing streaks for stronger teams\n- **AFK/Must-Play System**: Track player availability and enforce sat-out priority\n- **Persistent Data**: Player rosters and session state saved locally\n- **Google Sheets Sync**: Share a player roster with co-pugmasters via Google Sheets — bidirectional manual sync with per-field conflict resolution\n\n## Google Sheets Sync\n\nMultiple pugmasters can share a single player roster via a Google Sheet. Changes are synced manually — no auto-sync or polling.\n\n### Getting Started\n\n1. **Sign In**: Click the **Sheets** dropdown in the header → **Sign in with Google**. This opens your browser for OAuth consent. The app requests only the permissions it needs (Sheets + Drive for creating files).\n\n2. **Set Up a Sheet** (pick one):\n   - **Create New Sheet** — Generates a template with proper headers, rank dropdowns, and an Info tab\n   - **Connect Existing Sheet** — Paste a Google Sheets URL. The app validates that it has a \"Roster\" tab with the correct headers\n   - **Upload Roster** — Creates a new sheet pre-populated with all your current local players\n\n3. **Sync**: Click the **↻ Sync** button. The app reads the sheet, compares it to your local data, and shows a diff modal if there are changes.\n\n### Sync Workflow\n\n- **No changes**: Shows \"Everything is up to date\" toast\n- **Changes detected**: Opens the Sync Diff Modal showing:\n  - **Modified players** — Per-field conflict resolution. Click a field to choose local or remote value. Win counts default to the higher value.\n  - **New from Sheet** — Remote-only players with import toggle\n  - **New Locally** — Local-only players with push-to-sheet toggle\n- **Bulk actions**: \"Accept All Remote\" / \"Accept All Local\" buttons\n- **Cancel**: Closes the modal with no changes applied\n\n### First Sync\n\nWhen syncing for the first time (no previous sync recorded), the app defaults to **Accept All Remote** — the sheet roster replaces your local data. A banner explains this. Review the diff before applying.\n\n### Unsynced Changes\n\nAn amber dot appears on the Sync button when you've made local changes (adding/editing/removing players) since the last sync. The badge clears after a successful sync.\n\n### Token Storage\n\nOAuth tokens are stored in your OS keychain (Windows Credential Manager). The refresh token persists across app restarts — you won't need to sign in again unless you explicitly sign out or revoke access.\n\n### Disconnecting\n\nOpen the Sheets dropdown → **Disconnect**. This clears the sheet connection but keeps your local player data. If you have unsynced changes, the app warns before disconnecting.\n\n## Game Modes\n\n| Mode | Ranks Used | Hero Pool | Composition | Archetype Checks |\n|------|-----------|-----------|-------------|------------------|\n| **Stadium 5v5** | Stadium ranks (Rookie → Legend) | 31 Stadium-eligible heroes | 1T/2D/2S | Yes |\n| **Regular 5v5** | Competitive ranks (Bronze → Champion) | All 52 heroes | 1T/2D/2S | No |\n| **Regular 6v6** | Competitive ranks (Bronze → Champion) | All 52 heroes | 2T/2D/2S | No |\n\n### Switching Modes\n\n1. Click the mode badge in the top-right header\n2. Select a new mode from the dropdown\n3. Confirm the switch (session stats will reset)\n\n**Tip**: Export your CSV before switching to save session wins.\n\n## Installation\n\n### Windows\n\n1. Download the latest release:\n   - `Swoos PUGs Balancer_x.x.x_x64-setup.exe` (NSIS installer - recommended)\n   - Or `Swoos PUGs Balancer_x.x.x_x64_en-US.msi` (MSI installer)\n\n2. Run the installer and follow the prompts\n\n3. Launch \"Swoo's PUGs Balancer\" from the Start Menu\n\n**Note**: The app requires WebView2 runtime. The installer will automatically download it if not present.\n\n## Quick Start\n\n1. **Add Players**: \n   - Click the **\"+ Add\"** button and fill in each player's info (name, ranks, roles, heroes)\n   - *Or* for bulk import: Download the CSV template, fill it in, and drag-and-drop\n\n2. **Build Lobby**:\n   - Select 10+ players for the current lobby\n   - Mark AFK players as needed\n   - Set any soft constraints (together/apart)\n\n3. **Balance Teams**:\n   - Click \"Balance Teams\"\n   - Review warnings and team compositions\n   - Use lock buttons to keep specific players on teams\n   - Click \"Reshuffle\" to try new combinations\n\n4. **Record Results**:\n   - Click \"🏆 Team 1 Won\" or \"🏆 Team 2 Won\"\n   - Loss streaks update automatically\n   - Sat-out players are marked as must-play\n\n## Captain Draft Mode\n\nFor captain-pick sessions, use the **Draft** tab instead of auto-balancing:\n\n1. **Switch to Draft** — Click the \"👥 Draft\" toggle at the top of the right panel\n2. **Assign players** — Click a player in the Unassigned Pool → \"→ Team 1\" or \"→ Team 2\" (or drag-and-drop)\n3. **Cycle roles** — Click the role badge (T/D/S) on an assigned player to cycle through their willing roles\n4. **Unassign** — Click an assigned player to return them to the pool (or drag back)\n5. **Fill Remaining** — After captains draft a few picks, click \"⚡ Fill Remaining\" to auto-balance the rest\n6. **Post-match choice** — After recording a winner, choose \"⚖️ Auto-Balance Next Game\" or \"👥 Draft Next Game\"\n7. **New Game** — Click \"🔄 New Game\" to clear teams without recording a result\n\nThe draft view shows composition warnings (role overflows) and dims players who can't fill any remaining open role.\n\n## CSV Format\n\nSee [CSV Format Guide](docs/csv-format.md) for detailed column specifications.\n\nRequired columns:\n- `battletag` - Player's BattleTag (e.g., `Player#1234`)\n- `roles_willing` - Comma-separated roles: `Tank`, `DPS`, `Support`\n\nStadium rank columns:\n- `tank_rank`, `dps_rank`, `support_rank` - Stadium ranks (e.g., `Pro 2`, `Elite 1`)\n\nRegular competitive rank columns:\n- `tank_comp_rank`, `dps_comp_rank`, `support_comp_rank` - Per-role comp ranks\n- `regular_comp_rank` - Global fallback for all roles\n\nOther optional columns:\n- `role_preference` - Preferred role order\n- `hero_pool` - Heroes the player plays\n- `weight_modifier` - SR adjustment (-1000 to +1000)\n- `stadium_wins`, `regular_5v5_wins`, `regular_6v6_wins` - Mode-specific win counts\n- `notes` - Any notes about the player\n\n## How the Balancer Works\n\nThe balancer uses **multi-restart simulated annealing** to find the best team compositions:\n\n1. **Random Start**: Randomly assigns players to valid team slots (respecting role locks and must-play rules)\n2. **Simulated Annealing**: Tries ~1,000 random moves per restart, each chosen randomly from four types:\n   - **Inter-team swap (35%)** — Pick one player from each team and swap them across teams, each inheriting the other's role slot\n   - **2-opt swap (5%)** — Pick *two* pairs of players across teams and swap both pairs simultaneously. This breaks plateaus where no single swap helps but two coordinated swaps do (e.g., a Tank and a DPS need to trade teams together for a net improvement)\n   - **Bench swap (30%)** — Replace a playing player with a benched one in the same role slot\n   - **Intra-team role swap (30%)** — Swap the roles of two players on the same team (e.g., a flex playing Tank switches to DPS with a teammate)\n   \n   Early iterations accept slightly worse moves (with decaying probability) to escape shallow local minima. Temperature decays linearly to zero, so late iterations are strictly greedy. The best-ever state is tracked and restored at the end.\n3. **Multi-Restart**: Repeats this process 20 times with different random starting arrangements to avoid getting stuck on a local optimum\n4. **Best Result**: Returns the best composition found across all restarts\n\n**Complexity**: O(R × I × T) time, O(N) space — where R = restarts (20), I = iterations per restart (1,000), T = team size, and N = number of players. Bounded by iteration count, not combinatorics.\n\n### Scoring\n\nEach metric is **soft-normalized** using `x/(x+k)` then multiplied by an importance weight. Unlike hard clipping, this always provides gradient — the optimizer can distinguish 5 constraint violations from 10, rather than treating both as \"maxed out\":\n\n| Factor | Weight | Half-point (k) | Description |\n|--------|--------|-----------|-------------|\n| Per-Role Matchup | 500 | 4000 | Role-vs-role SR gaps (e.g., GM tank vs Gold tank) |\n| One-Trick Conflicts | 200 | 4 | Two one-tricks on same hero, same team (Stadium only) |\n| Archetype Parity | 150 | binary | Flyer without opposing hitscan (Stadium only) |\n| Soft Constraints | 120 | 5 | Together/apart preference violations |\n| SR Variance | 100 | 800 | Teams with different internal skill spread |\n| Role Preference | 50 | 20 | Playing 3rd-choice or worse roles (2nd-choice is fine) |\n\n## Stadium Rank Tiers\n\n| Tier | Base SR |\n|------|---------|\n| Rookie | 1000 |\n| Novice | 1500 |\n| Contender | 2000 |\n| Elite | 2500 |\n| Pro | 3000 |\n| All-Star | 3500 |\n| Legend | 4000 |\n\nSub-ranks (1-5) add 0-400 SR. Example: `Pro 1` = 3400 SR, `Pro 5` = 3000 SR\n\n## Troubleshooting\n\nSee [Troubleshooting Guide](docs/troubleshooting.md) for common issues.\n\n## Development\n\n### Prerequisites\n\n- Node.js 18+\n- Rust 1.70+ (for Tauri)\n- Windows 10/11 with WebView2 Runtime\n\n### Setup\n\n```bash\n# Install dependencies\nnpm install\n\n# Start web dev server (browser only)\nnpm run dev\n\n# Start Tauri dev mode (desktop app with hot reload)\nnpm run tauri:dev\n```\n\n### Google Cloud Credentials\n\nTo enable Google Sheets sync during development, you need your own OAuth client ID. See [Google Cloud Setup Guide](docs/google-cloud-setup.md) for step-by-step instructions.\n\n### Testing\n\n```bash\n# Run unit tests\nnpx vitest run\n\n# Run tests in watch mode\nnpx vitest\n```\n\n### Building\n\n```bash\n# Build production installers (MSI + NSIS)\nnpm run tauri:build\n```\n\nOutput locations after build:\n- **EXE**: `src-tauri/target/release/app.exe` (standalone, no install required)\n- **MSI**: `src-tauri/target/release/bundle/msi/Swoos PUGs Balancer_x.x.x_x64_en-US.msi`\n- **NSIS**: `src-tauri/target/release/bundle/nsis/Swoos PUGs Balancer_x.x.x_x64-setup.exe`\n\n### Running the Built App\n\nAfter building, you can run the app directly without installing:\n\n```bash\n# Run the standalone executable\n./src-tauri/target/release/app.exe\n```\n\nOr install via one of the installer packages for Start Menu integration.\n\n## Tech Stack\n\n- **Frontend**: React 18 + TypeScript + Tailwind CSS v4\n- **State**: Zustand with localStorage persistence\n- **Desktop**: Tauri 2.x (Rust)\n- **Auth**: OAuth 2.0 PKCE with Google (tokens stored in OS keychain via Tauri)\n- **Sync**: Google Sheets API (REST, called via Tauri HTTP plugin — no npm Google libs)\n- **Testing**: Vitest\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshantingliu%2Fpugs-balancer-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshantingliu%2Fpugs-balancer-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshantingliu%2Fpugs-balancer-app/lists"}