From 5382bf96e3d7436b7d4e3ec15100febc24ddbfd6 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Mon, 24 Aug 2026 20:15:07 -0400 Subject: [PATCH] [match] add tachyon server configuration --- .env.example | 7 +++++++ apps/match/src/context.ts | 12 ++++++++++++ apps/match/src/match.app.ts | 18 ++++++++++-------- apps/match/src/openapi.ts | 11 +++++++---- apps/match/wrangler.jsonc | 5 +++-- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 16d7490..52de3dc 100644 --- a/.env.example +++ b/.env.example @@ -108,6 +108,13 @@ RECFLARE_DOMAIN=rec.example.com # RECFLARE_PHOTON_VOICE_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 # 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 diff --git a/apps/match/src/context.ts b/apps/match/src/context.ts index 2c33b7f..1fd3dbf 100644 --- a/apps/match/src/context.ts +++ b/apps/match/src/context.ts @@ -52,6 +52,18 @@ export type Env = SharedHonoEnv & { PHOTON_VOICE_APP_ID?: string /** The Photon Chat application id. Optional; see {@link Env.PHOTON_REALTIME_APP_ID}. */ 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 * info and the one stamped on every room instance, which must agree. Optional; unlike the diff --git a/apps/match/src/match.app.ts b/apps/match/src/match.app.ts index 7cd4562..f7a4251 100644 --- a/apps/match/src/match.app.ts +++ b/apps/match/src/match.app.ts @@ -2150,8 +2150,9 @@ const app = new Hono() 'The realtime (Photon) credentials the caller should connect with, in a', '`{ success, value, error }` envelope: a freshly minted `photonAuthToken`, the', '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', - 'no separate voice server, so the voice fields are null. `experiments` carries the', + '(from their presence, falling back to the `roomInstanceId` query param). The voice', + 'fields carry the Tachyon voice server (`TACHYON_HOST_PORT`/`TACHYON_NAME`),', + 'empty when none is configured. `experiments` carries the', 'client’s networking flags.', ].join(' '), security: AUTHED, @@ -2205,12 +2206,13 @@ const app = new Hono() photonAuthToken, ...apps, photonRoomId, - // Empty strings rather than null: there's no separate voice server either - // way, and the client's decoder is likelier to accept a missing-value string - // than a null on a string field. The presence payload's - // NULL_CONNECTION_INFO keeps its nulls — that one never carries credentials. - voiceConnectionInfo: '', - voiceServerId: '', + // The Tachyon voice server, from the operator's vars — empty strings when + // unset (no separate voice server). Empty rather than null: the client's + // decoder is likelier to accept a missing-value string than a null on a + // string field. The presence payload's NULL_CONNECTION_INFO keeps its + // nulls — that one never carries credentials. + voiceConnectionInfo: varOr(c.env.TACHYON_HOST_PORT, ''), + voiceServerId: varOr(c.env.TACHYON_NAME, ''), experiments: PHOTON_EXPERIMENTS, }, error: null, diff --git a/apps/match/src/openapi.ts b/apps/match/src/openapi.ts index c9d65a7..c5f1abe 100644 --- a/apps/match/src/openapi.ts +++ b/apps/match/src/openapi.ts @@ -205,8 +205,9 @@ export const ConnectionExperiments = z.object({ * `{ success, value, error }` envelope. The applications and region are fixed for * 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 - * — the same name every other player in that instance is handed. There's no separate - * voice server, so both voice fields are null. `photonRegion` matches the one stamped + * — the same name every other player in that instance is handed. The voice fields name + * 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. */ export const ConnectionInfo = z.object({ @@ -216,8 +217,10 @@ export const ConnectionInfo = z.object({ photonChatAppId: z.string().describe('Photon Chat application id'), photonRegion: z.string().describe('Region id, matching a room instance’s `photonRegion`'), photonRoomId: z.string().describe('The caller’s current instance; empty when they’re in none'), - voiceConnectionInfo: z.literal('').describe('Empty — no separate voice server'), - voiceServerId: z.literal('').describe('Empty — no separate voice server'), + voiceConnectionInfo: z + .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, }) diff --git a/apps/match/wrangler.jsonc b/apps/match/wrangler.jsonc index 77522aa..a6a9d19 100644 --- a/apps/match/wrangler.jsonc +++ b/apps/match/wrangler.jsonc @@ -66,9 +66,10 @@ "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, - // 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_, see .env.example), so swapping a room out or // 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