[match] add tachyon server configuration

This commit is contained in:
Devin Zuczek
2026-08-24 20:15:07 -04:00
parent b8d5103d57
commit 5382bf96e3
5 changed files with 39 additions and 14 deletions
+7
View File
@@ -108,6 +108,13 @@ RECFLARE_DOMAIN=rec.example.com
# RECFLARE_PHOTON_VOICE_APP_ID= # RECFLARE_PHOTON_VOICE_APP_ID=
# RECFLARE_PHOTON_CHAT_APP_ID= # RECFLARE_PHOTON_CHAT_APP_ID=
# The Tachyon voice server (`match`, GET /player/connection-info): the `host:port` the
# client is handed as `voiceConnectionInfo`, and its id as `voiceServerId`. EMPTY unless
# you set them — no separate voice server. Set both or neither; like the Photon ids they
# are not secrets (the client receives them in the clear).
# RECFLARE_TACHYON_HOST_PORT=127.0.0.1:7777
# RECFLARE_TACHYON_NAME=server-1
# The Photon region every session is pinned to (`match`). Unlike the app ids above this # The Photon region every session is pinned to (`match`). Unlike the app ids above this
# does default, to `us` (us-east1) — an instance stamped with an empty region is one the # does default, to `us` (us-east1) — an instance stamped with an empty region is one the
# client cannot connect to, so there is no "unset" state for it to have. It is # client cannot connect to, so there is no "unset" state for it to have. It is
+12
View File
@@ -52,6 +52,18 @@ export type Env = SharedHonoEnv & {
PHOTON_VOICE_APP_ID?: string PHOTON_VOICE_APP_ID?: string
/** The Photon Chat application id. Optional; see {@link Env.PHOTON_REALTIME_APP_ID}. */ /** The Photon Chat application id. Optional; see {@link Env.PHOTON_REALTIME_APP_ID}. */
PHOTON_CHAT_APP_ID?: string PHOTON_CHAT_APP_ID?: string
/**
* The Tachyon voice server the client is handed as `voiceConnectionInfo` on
* `GET /player/connection-info`, as `host:port` (e.g. `66.228.47.217:7777`).
* Optional, and EMPTY when unset — no separate voice server. Not a secret (the
* client receives it in the clear), so a plain var like the Photon ids.
*/
TACHYON_HOST_PORT?: string
/**
* The id of that voice server, handed to the client as `voiceServerId` (e.g.
* `server-1`). Optional; see {@link Env.TACHYON_HOST_PORT} — set both or neither.
*/
TACHYON_NAME?: string
/** /**
* The Photon region every session is pinned to — both the region named in the connection * 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 * info and the one stamped on every room instance, which must agree. Optional; unlike the
+10 -8
View File
@@ -2150,8 +2150,9 @@ const app = new Hono<App>()
'The realtime (Photon) credentials the caller should connect with, in a', 'The realtime (Photon) credentials the caller should connect with, in a',
'`{ success, value, error }` envelope: a freshly minted `photonAuthToken`, the', '`{ success, value, error }` envelope: a freshly minted `photonAuthToken`, the',
'Photon application ids, and the `photonRoomId` of the instance the caller is in', 'Photon application ids, and the `photonRoomId` of the instance the caller is in',
'(from their presence, falling back to the `roomInstanceId` query param). There is', '(from their presence, falling back to the `roomInstanceId` query param). The voice',
'no separate voice server, so the voice fields are null. `experiments` carries the', 'fields carry the Tachyon voice server (`TACHYON_HOST_PORT`/`TACHYON_NAME`),',
'empty when none is configured. `experiments` carries the',
'clients networking flags.', 'clients networking flags.',
].join(' '), ].join(' '),
security: AUTHED, security: AUTHED,
@@ -2205,12 +2206,13 @@ const app = new Hono<App>()
photonAuthToken, photonAuthToken,
...apps, ...apps,
photonRoomId, photonRoomId,
// Empty strings rather than null: there's no separate voice server either // The Tachyon voice server, from the operator's vars — empty strings when
// way, and the client's decoder is likelier to accept a missing-value string // unset (no separate voice server). Empty rather than null: the client's
// than a null on a string field. The presence payload's // decoder is likelier to accept a missing-value string than a null on a
// NULL_CONNECTION_INFO keeps its nulls — that one never carries credentials. // string field. The presence payload's NULL_CONNECTION_INFO keeps its
voiceConnectionInfo: '', // nulls — that one never carries credentials.
voiceServerId: '', voiceConnectionInfo: varOr(c.env.TACHYON_HOST_PORT, ''),
voiceServerId: varOr(c.env.TACHYON_NAME, ''),
experiments: PHOTON_EXPERIMENTS, experiments: PHOTON_EXPERIMENTS,
}, },
error: null, error: null,
+7 -4
View File
@@ -205,8 +205,9 @@ export const ConnectionExperiments = z.object({
* `{ success, value, error }` envelope. The applications and region are fixed for * `{ success, value, error }` envelope. The applications and region are fixed for
* recflare; what varies per caller is `photonAuthToken` (minted for them on the spot) * recflare; what varies per caller is `photonAuthToken` (minted for them on the spot)
* and `photonRoomId`, the Photon room of the instance their presence says they're in * and `photonRoomId`, the Photon room of the instance their presence says they're in
* — the same name every other player in that instance is handed. There's no separate * — the same name every other player in that instance is handed. The voice fields name
* voice server, so both voice fields are null. `photonRegion` matches the one stamped * the Tachyon voice server (`TACHYON_HOST_PORT`/`TACHYON_NAME` vars), empty when none
* is configured. `photonRegion` matches the one stamped
* on every room instance, so the two can't disagree. * on every room instance, so the two can't disagree.
*/ */
export const ConnectionInfo = z.object({ export const ConnectionInfo = z.object({
@@ -216,8 +217,10 @@ export const ConnectionInfo = z.object({
photonChatAppId: z.string().describe('Photon Chat application id'), photonChatAppId: z.string().describe('Photon Chat application id'),
photonRegion: z.string().describe('Region id, matching a room instances `photonRegion`'), photonRegion: z.string().describe('Region id, matching a room instances `photonRegion`'),
photonRoomId: z.string().describe('The callers current instance; empty when theyre in none'), photonRoomId: z.string().describe('The callers current instance; empty when theyre in none'),
voiceConnectionInfo: z.literal('').describe('Empty — no separate voice server'), voiceConnectionInfo: z
voiceServerId: z.literal('').describe('Empty — no separate voice server'), .string()
.describe('The Tachyon voice server, `host:port`; empty when none is configured'),
voiceServerId: z.string().describe('The Tachyon voice server id; empty when none is configured'),
experiments: ConnectionExperiments, experiments: ConnectionExperiments,
}) })
+3 -2
View File
@@ -66,9 +66,10 @@
"head_sampling_rate": 1 // 100% "head_sampling_rate": 1 // 100%
} }
}, },
// The operator's knobs — the room substitutions (ROOM_REDIRECTS) and the Photon app ids // The operator's knobs — the room substitutions (ROOM_REDIRECTS), the Photon app ids
// and region (PHOTON_REALTIME_APP_ID, PHOTON_VOICE_APP_ID, PHOTON_CHAT_APP_ID, // and region (PHOTON_REALTIME_APP_ID, PHOTON_VOICE_APP_ID, PHOTON_CHAT_APP_ID,
// PHOTON_REGION) — are deliberately NOT set here. They're injected at deploy time from // PHOTON_REGION), and the Tachyon voice server (TACHYON_HOST_PORT, TACHYON_NAME) —
// are deliberately NOT set here. They're injected at deploy time from
// the gitignored .env (RECFLARE_<VAR>, see .env.example), so swapping a room out or // the gitignored .env (RECFLARE_<VAR>, see .env.example), so swapping a room out or
// pointing at your own Photon apps never means editing a versioned file. Unset — the // pointing at your own Photon apps never means editing a versioned file. Unset — the
// default — means every matchmake enters the room it asked for, the Photon app ids are // default — means every matchmake enters the room it asked for, the Photon app ids are