[2026] split 2026 to tachy hosts

This commit is contained in:
Devin Zuczek
2026-10-20 21:21:24 -04:00
parent affdcb85e3
commit ed45f38137
6 changed files with 106 additions and 15 deletions
+15 -3
View File
@@ -87,11 +87,23 @@ export type Env = SharedHonoEnv & {
* The builds under staged rollout, overriding `ROLLOUT_GAME_VERSIONS` in @repo/domain:
* a comma-separated list of builds, or `off` for none. Set for every worker from the
* root `.env` (`RECFLARE_ROLLOUT_GAME_VERSIONS`), so this agrees with the gate `auth`
* applies by construction. Read through `rolloutGameBuilds`; decides who is served the
* rollout Tachyon pool above. Ending the rollout (`off`) moves those callers to the
* live pool.
* applies by construction. Read through `rolloutGameBuilds`; the DEFAULT for which
* builds are served the rollout Tachyon pool above (see `TACHYON_ROLLOUT_GAME_VERSIONS`),
* so ending the rollout (`off`) moves those callers to the live pool unless that var
* keeps them.
*/
ROLLOUT_GAME_VERSIONS?: string
/**
* The builds served the rollout Tachyon pool, decoupled from the sign-in gate
* (`RECFLARE_TACHYON_ROLLOUT_GAME_VERSIONS` in `.env`): the same comma-separated list of
* builds, or `off` for none. Unset means `ROLLOUT_GAME_VERSIONS` decides, as above. Set,
* it is the only thing that does — so a build opened to everyone (`ROLLOUT_GAME_VERSIONS=off`)
* can stay on its own voice servers by naming it here. Matched as a BUILD: a listed
* `20260323` (or `20260323.14` — the point release is stripped) covers every point
* release of it, the way every other version gate compares. Read through
* `tachyonRolloutGameBuilds` in match.app.ts.
*/
TACHYON_ROLLOUT_GAME_VERSIONS?: string
/**
* The Photon region every session is pinned to — both the region named in the connection
* info and the one stamped on every room instance, which must agree. Optional; unlike the
+28 -9
View File
@@ -43,6 +43,7 @@ import {
MatchmakingErrorCode,
MessageType,
MOST_ACTIVE_CLUBHOUSE_LIMIT,
parseGameBuilds,
putPlayerSettingsIfChanged,
readPlayerSettings,
recordRoomVisit,
@@ -254,10 +255,24 @@ const ROLLOUT_TACHYON_SERVER_ID_PREFIX = 'rollout-'
interface TachyonCaller {
/** The account's `isSandbox` flag. */
isSandbox: boolean
/** Running a build under staged rollout (`rolloutGameBuilds`, off the token's `rn.ver`). */
/** Running a build served the rollout pool (`tachyonRolloutGameBuilds`, off the token's `rn.ver`). */
isRollout: boolean
}
/**
* The builds served the rollout Tachyon pool: the operator's `TACHYON_ROLLOUT_GAME_VERSIONS`
* when set (`off` for none), otherwise the builds under staged rollout
* ({@link rolloutGameBuilds}, the same list `auth` gates sign-in on). The override exists
* because the two lists part ways at general availability: `ROLLOUT_GAME_VERSIONS=off`
* opens the new build to everyone, and without this it would also move every player on it
* onto the live voice servers. Entries are reduced to builds by {@link parseGameBuilds},
* so `20260323.14` lists the same build as `20260323`, and the caller's version is
* compared the same way by {@link isRolloutGameVersion}.
*/
function tachyonRolloutGameBuilds(env: Env): string[] {
return parseGameBuilds(env.TACHYON_ROLLOUT_GAME_VERSIONS) ?? rolloutGameBuilds(env)
}
/**
* The pool the caller's instance is picked out of — one of three, each the same
* comma-separated list, with selection inside it the same instance-id round-robin
@@ -265,10 +280,12 @@ interface TachyonCaller {
*
* - `TACHYON_HOST_PORT_SANDBOX` for a SANDBOX account, which runs against its own servers
* apart from the live pool;
* - `TACHYON_HOST_PORT_ROLLOUT` for a caller on a build under staged rollout — the build
* `auth` only lets Discord supporters sign in on, which gets its own voice servers while
* it rolls out. Sessions are already split by build ({@link callerGameVersion}), so a
* rollout instance's players are all on that build and all land here together;
* - `TACHYON_HOST_PORT_ROLLOUT` for a caller on a build in {@link tachyonRolloutGameBuilds}
* — by default the build under staged rollout, which `auth` only lets Discord supporters
* sign in on and which gets its own voice servers while it rolls out;
* `TACHYON_ROLLOUT_GAME_VERSIONS` keeps a build there after the gate opens. Sessions are
* already split by build ({@link callerGameVersion}), so a rollout instance's players
* are all on that build and all land here together;
* - `TACHYON_HOST_PORT`, the live pool, for everyone else.
*
* Sandbox wins when both apply: a sandbox account on the rollout build is still a sandbox
@@ -3217,8 +3234,10 @@ const app = new Hono<App>()
'…). Both are empty when the pool is unset or the caller is in no instance. A',
'sandbox account is picked out of the `TACHYON_HOST_PORT_SANDBOX` pool instead, the',
'same way, with ids `dev-1`, `dev-2`, …; a caller whose token’s `rn.ver` is a build',
'under staged rollout (`ROLLOUT_GAME_VERSIONS`, by default `20260323`) out of',
'`TACHYON_HOST_PORT_ROLLOUT`, with ids `rollout-1`, `rollout-2`, …. Sandbox wins when',
'under staged rollout (`ROLLOUT_GAME_VERSIONS`, by default `20260323`; or whatever',
'`TACHYON_ROLLOUT_GAME_VERSIONS` names instead, so a build can keep its own voice',
'servers after its rollout ends) out of `TACHYON_HOST_PORT_ROLLOUT`, with ids',
'`rollout-1`, `rollout-2`, …. Sandbox wins when',
'both apply, and either special pool left unset falls back to the live one.',
'`experiments` carries the client’s networking flags.',
].join(' '),
@@ -3261,11 +3280,11 @@ const app = new Hono<App>()
const account = await getAccount(c.env.DB, id)
// The instance's server, the same one every other player in it is handed — except
// that a SANDBOX account is picked out of the sandbox pool, and a caller on a
// rollout build out of the rollout pool, instead of the live one.
// rollout-pool build out of the rollout pool, instead of the live one.
const tachyon = tachyonServerFor(
tachyonPoolFor(c.env, {
isSandbox: account?.isSandbox === true,
isRollout: isRolloutGameVersion(await callerVersion(c), rolloutGameBuilds(c.env)),
isRollout: isRolloutGameVersion(await callerVersion(c), tachyonRolloutGameBuilds(c.env)),
}),
roomInstanceId
)
@@ -1815,8 +1815,10 @@ describe('auth-gated endpoints', () => {
const originalSandbox = env.TACHYON_HOST_PORT_SANDBOX
const originalRollout = env.TACHYON_HOST_PORT_ROLLOUT
const originalRolloutVersions = env.ROLLOUT_GAME_VERSIONS
const originalTachyonRolloutVersions = env.TACHYON_ROLLOUT_GAME_VERSIONS
try {
env.TACHYON_HOST_PORT = '198.51.100.12:7777'
env.TACHYON_ROLLOUT_GAME_VERSIONS = undefined
env.TACHYON_HOST_PORT_SANDBOX = '203.0.113.30:7777, 203.0.113.30:7778'
const voiceFor = async (player: string, roomInstanceId?: number) => {
@@ -1928,6 +1930,50 @@ describe('auth-gated endpoints', () => {
voiceConnectionInfo: '198.51.100.12:7777',
voiceServerId: 'tachyon-1',
})
// TACHYON_ROLLOUT_GAME_VERSIONS decouples the pool from the gate: set, it alone
// decides which builds get the rollout pool, so a build opened to everyone
// (ROLLOUT_GAME_VERSIONS=off) keeps its own voice servers while the 2025 build
// stays live. Matched as a build — a listed point release covers every point
// release of it, and a token carrying the bare build matches a listed one.
env.ROLLOUT_GAME_VERSIONS = 'off'
const keptRollout = await createRoomInstance(env.DB, {
ownerAccountId: 976,
roomId: 2,
photonRoomId: 'tachyon-rollout-kept',
maxCapacity: 12,
})
for (const listed of ['20260323', '20260323.14', ' 20260323.2 ,20231207']) {
env.TACHYON_ROLLOUT_GAME_VERSIONS = listed
for (const version of ['20260323', '20260323.14', '20260323.99']) {
expect(await rolloutVoiceFor('976', version, keptRollout.roomInstanceId)).toEqual(
rolloutPool[keptRollout.roomInstanceId % rolloutPool.length]
)
}
expect(await rolloutVoiceFor('976', '20250718.01', keptRollout.roomInstanceId)).toEqual({
voiceConnectionInfo: '198.51.100.12:7777',
voiceServerId: 'tachyon-1',
})
}
// Sandbox still wins over it.
env.TACHYON_ROLLOUT_GAME_VERSIONS = '20260323'
expect(await rolloutVoiceFor('974', '20260323.14', keptRollout.roomInstanceId)).toEqual(
sandboxPool[keptRollout.roomInstanceId % sandboxPool.length]
)
// Set, it overrides the gate's list in BOTH directions: `off` sends a build still
// under rollout to the live pool.
env.ROLLOUT_GAME_VERSIONS = '20260323'
env.TACHYON_ROLLOUT_GAME_VERSIONS = 'off'
expect(await rolloutVoiceFor('976', '20260323.14', keptRollout.roomInstanceId)).toEqual({
voiceConnectionInfo: '198.51.100.12:7777',
voiceServerId: 'tachyon-1',
})
// Blank is unset: the gate's list decides again.
env.TACHYON_ROLLOUT_GAME_VERSIONS = ''
expect(await rolloutVoiceFor('976', '20260323.14', keptRollout.roomInstanceId)).toEqual(
rolloutPool[keptRollout.roomInstanceId % rolloutPool.length]
)
env.TACHYON_ROLLOUT_GAME_VERSIONS = undefined
env.ROLLOUT_GAME_VERSIONS = originalRolloutVersions
// With no rollout pool configured, a rollout build falls back to the live one, not
@@ -1961,6 +2007,7 @@ describe('auth-gated endpoints', () => {
env.TACHYON_HOST_PORT_SANDBOX = originalSandbox
env.TACHYON_HOST_PORT_ROLLOUT = originalRollout
env.ROLLOUT_GAME_VERSIONS = originalRolloutVersions
env.TACHYON_ROLLOUT_GAME_VERSIONS = originalTachyonRolloutVersions
}
})