From 625cec113b03ea7539e661f0f3b70eb626c7d5f3 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Fri, 2 Oct 2026 15:56:24 -0400 Subject: [PATCH] [match] on 2023 do not room switch --- .env.example | 3 ++- apps/match/README.md | 2 ++ apps/match/src/match.app.ts | 7 +++++++ apps/match/src/test/integration/api.test.ts | 21 +++++++++++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 9bd74cc..e9f94c0 100644 --- a/.env.example +++ b/.env.example @@ -117,7 +117,8 @@ RECFLARE_DOMAIN=rec.example.com # it came through the room list, a club's clubhouse, or a party. Substitution is a single # hop (2=3,3=2 swaps the two rooms), a requested subroom is dropped in favour of the # substitute's default one, and a target that doesn't exist leaves the original room in -# place. Following a friend or joining a specific instance is unaffected — those join a +# place. Only client builds newer than 20230414 are switched; the 2023 client and older +# enter the room they asked for. Following a friend or joining a specific instance is unaffected — those join a # live instance, which is already in whichever room it was created in. # RECFLARE_ROOM_REDIRECTS=2=MyHub diff --git a/apps/match/README.md b/apps/match/README.md index cd100f0..b1fda6c 100644 --- a/apps/match/README.md +++ b/apps/match/README.md @@ -112,6 +112,8 @@ substitutes the same as asking by id. - **One hop only** — `2=3,3=2` swaps the two rooms rather than looping. - **An unresolvable target leaves the original room in place** (logged), so a typo doesn't make a room unreachable. +- **Only builds newer than `20230414` are substituted** — a caller on the 2023 client or + an older one (or on a token that names no build) enters the room they asked for. - **Following a friend and joining a specific instance are unaffected** — those enter a live instance, which is already in whichever room it was created in. diff --git a/apps/match/src/match.app.ts b/apps/match/src/match.app.ts index aed16ba..cda2b5b 100644 --- a/apps/match/src/match.app.ts +++ b/apps/match/src/match.app.ts @@ -1389,6 +1389,10 @@ function roomRedirects(env: Env): Map { * default subroom. Substitution is a single hop — `2=3,3=2` swaps the two rooms rather * than looping — and an unresolvable target leaves the original room in place, so a * typo'd knob degrades to "no substitution" instead of a dead hub. + * + * Only builds NEWER than the 2023 client ({@link BUILD_2023}) are substituted. A caller on + * that build or an older one — or on a token that names none, which matchmakes as + * `GAME_VERSION` (see {@link callerGameVersion}) — enters the room they asked for. */ async function substituteRoom( c: Context, @@ -1399,6 +1403,9 @@ async function substituteRoom( const to = roomRedirects(c.env).get(fromId) if (to === undefined) return { room, subRoomId } + const build = buildNumber(await callerGameVersion(c)) + if (build === null || build <= BUILD_2023) return { room, subRoomId } + const toId = Number.parseInt(to, 10) const target = Number.isNaN(toId) ? await getRoomByName(c.env.DB, to) diff --git a/apps/match/src/test/integration/api.test.ts b/apps/match/src/test/integration/api.test.ts index 14ac1c7..aae058e 100644 --- a/apps/match/src/test/integration/api.test.ts +++ b/apps/match/src/test/integration/api.test.ts @@ -1132,12 +1132,14 @@ describe('public endpoints', () => { test('ROOM_REDIRECTS switches a matchmake out to another room', async () => { // `env` is shared by every test in this file, so restore the knob in `finally`. const original = env.ROOM_REDIRECTS - const matchmake = async (path: string, player: string) => + // Substitution only applies to builds newer than 20230414, so matchmake as the 2025 one + // unless the case says otherwise. + const matchmake = async (path: string, player: string, version = '20250718.01') => (await ( await exports.default.fetch(`${ORIGIN}${path}`, { method: 'POST', headers: { - ...(await bearer(player)), + ...(await bearer(player, version)), 'Content-Type': 'application/x-www-form-urlencoded', }, // Private, so each call gets a fresh instance of whatever room it landed in. @@ -1174,6 +1176,21 @@ describe('public endpoints', () => { roomId: 77, }) + // The 2023 build and anything older is never substituted, nor is a token naming no + // build (it matchmakes as GAME_VERSION): they enter the room they asked for. + for (const [player, version] of [ + ['8808', '20230414'], + ['8809', '20230414.01'], + ['8810', '20221117'], + ]) { + expect((await matchmake('/matchmake/room/2', player, version)).roomInstance).toMatchObject( + { roomId: 2, name: '^RecCenter' } + ) + } + expect((await matchmake('/matchmake/room/2', '8811', '')).roomInstance).toMatchObject({ + roomId: 2, + }) + // Targeting by id works the same, and substitution is a single hop: 2 and 77 // swap rather than bouncing between each other. env.ROOM_REDIRECTS = '2=77,77=2'