## Description: Implements 2v2 ranked matchmaking end-to-end against the matchmaking API's 2v2 queues (API PR #419): core team pinning, the server's second checkin loop and game creation, and the client UI. ## Core — deterministic team pinning The matcher's assignment specifies exactly who plays with whom (`teams: [[a,d],[b,c]]`), but team assignment previously only did clan/friend balancing and could scramble the ELO-balanced split. - `PlayerSchema`/`PlayerInfo` gain an optional **`teamIndex`** — a server-stamped index into the game's team list, part of `GameStartInfo` so it's identical on every client (same category as `clanTag`/`friends`, which already feed deterministic team assignment). - `assignTeams` honors pins **unconditionally** — before clan/friend grouping and past `maxTeamSize` (the matcher's balancing is authoritative) — and seeds the counts that balancing of any unpinned players sees. Pinned players still participate in the friend graph, so an unpinned friend is pulled toward a pinned player's team. - publicIds never enter core: the game server resolves publicId → teamIndex per client at game start. ## Server - **One checkin long-poll per mode.** Both loops send `mode` explicitly (the API deployed ahead of the client, so no omit-for-back-compat needed). - **`get2v2Config()`**: Team mode, `playerTeams: 2`, `maxPlayers: 4`, always-compact map, donations enabled (matching public team games), `rankedType: "2v2"` (the API's 2v2 ingestion has shipped; `RankedType` gains `TwoVTwo`). - **The assignment payload is now used** (it was previously discarded): `players` → `allowedPublicIds` so only the matched accounts can take the slots (also hardens 1v1), and `teams` → `teamIndex` stamps at game start. A malformed assignment logs a warning and falls back to creating the game without pins rather than stranding matched players. - The 3-clients-per-IP cap on public games applies to matchmade games too (an allowlist doesn't stop one person multi-tabbing multiple accounts). It is now skipped in dev, where local testing (multi-tab, the 4-player e2e) is inherently same-IP — matching the existing dev/prod gating of Turnstile and the duplicate-account kick. ## Client - Ranked modal's 2v2 card is enabled; it passes the mode through `open-matchmaking` (dispatchers without a detail — homepage button, requeue URL — still mean 1v1). - Matchmaking modal joins with `&mode=1v1`/`&mode=2v2`, shows a 2v2 title (`matchmaking_modal.title_2v2` in en.json), and shows the real 2v2 ELO from the new `leaderboard.twoVtwo` field in `/users/@me` (the ranked modal's 2v2 card does too). - WinModal shows requeue for any ranked game and carries the mode back into the right queue (`/?requeue=2v2`). - 2v2 ranked stats surface in the player stats tree (labeled via `player_stats_tree.ranked_2v2`). ## Harnesses (`tests/matchmaking/`) - Contained: the fake server captures the `mode` query param; asserts each queue sends its mode explicitly. **10/10.** - E2E: `MM_MODE=2v2` runs four real browser players through the real local worker's 2v2 queue and rides the flow into the started game. Asserts same gameId for all four, the 2v2 config, allowlist admission, and a **deterministic 2 vs 2 in-game split read from each client's GameView** (the software-WebGL gate is spoofed in test pages only). **8/8.** 1v1 e2e still **6/6.** ## Verification - `npm test`: 2,053 tests pass, including 7 new (6 `assignTeams` pinning unit tests + a full-game pinned-split test through `setup()`). - `npx tsc --noEmit`, ESLint clean. - Live e2e against a local `wrangler dev` API worker: 1v1 (6/6) and 2v2 (8/8) as above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory (UI changes — the ranked modal's 1v1/2v2 cards — were verified with before/after screenshots in the live app during development.) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
OpenFront.io is an online real-time strategy game focused on territorial control and alliance building. Players compete to expand their territory, build structures, and form strategic alliances in various maps based on real-world geography.
This is a fork/rewrite of WarFront.io. Credit to https://github.com/WarFrontIO.
License
OpenFront source code is licensed under the GNU Affero General Public License v3.0
Current copyright notices appear in:
- Footer: "© OpenFront and Contributors"
- Loading screen: "© OpenFront and Contributors"
Modified versions must preserve these notices in reasonably visible locations.
See the LICENSE for complete requirements.
For asset licensing, see LICENSE-ASSETS.
For license history, see LICENSING.md.
🌟 Features
- Real-time Strategy Gameplay: Expand your territory and engage in strategic battles
- Alliance System: Form alliances with other players for mutual defense
- Multiple Maps: Play across various geographical regions including Europe, Asia, Africa, and more
- Resource Management: Balance your expansion with defensive capabilities
- Cross-platform: Play in any modern web browser
📋 Prerequisites
- npm (v10.9.2 or higher)
- A modern web browser (Chrome, Firefox, Edge, etc.)
🚀 Installation
-
Clone the repository
git clone https://github.com/openfrontio/OpenFrontIO.git cd OpenFrontIO -
Install dependencies
npm run instDo NOT use
npm installnornpm ibut instead use ournpm run inst. It runs the safernpm ci --ignore-scriptsto install dependencies exactly according to the versions inpackage-lock.jsonand doesn't run scripts. This can prevent being hit by a supply chain attack.
🎮 Running the Game
Development Mode
Run both the client and server in development mode with live reloading:
npm run dev
This will:
- Start the webpack dev server for the client
- Launch the game server with development settings
- Open the game in your default browser (to disable this behavior, set
SKIP_BROWSER_OPEN=truein your environment)
Client Only
To run just the client with hot reloading:
npm run start:client
Server Only
To run just the server with development settings:
npm run start:server-dev
Connecting to staging or production backends
Sometimes it's useful to connect to production servers when replaying a game, testing user profiles, purchases, or login flow.
To replay a production game, make sure you're on the same commit that the game you want to replay was executed on, you can find the
gitCommitvalue viahttps://api.openfront.io/game/[gameId]. Unfinished games cannot be replayed on localhost.
To connect to staging api servers:
npm run dev:staging
To connect to production api servers:
npm run dev:prod
🛠️ Development Tools
-
Format code:
npm run format -
Lint code:
npm run lint -
Lint and fix code:
npm run lint:fix -
Testing
npm test
🏗️ Project Structure
/src/client- Frontend game client/src/core- Deterministic game simulation/src/server- Backend game server/resources- Static assets (images, maps, etc.)
🤝 Contributing
Contributions and translations are welcome! See CONTRIBUTING.md for the workflow, the approved-issue process, project governance, and translation info.