From 2b70e8fe34af7f3828f6c461ae804f7a7b7260e9 Mon Sep 17 00:00:00 2001 From: SchattenundLicht Date: Mon, 17 Aug 2026 18:04:10 +0200 Subject: [PATCH] Dateien nach "/" hochladen --- plan.md | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++ vergleichs.md | 218 +++++++++++++++++++++++++++++++++ 2 files changed, 545 insertions(+) create mode 100644 plan.md create mode 100644 vergleichs.md diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..d0e5688 --- /dev/null +++ b/plan.md @@ -0,0 +1,327 @@ +--- +name: Voice Architecture Plan +overview: "Development-ready SDK/ServerLib plan (Option B): custom Conan Voice Server on TeamSpeak 3 ServerLib + custom ClientLib app, with server-side whisper-list routing. Includes concrete SDK function names, init/shutdown order, data-flow, threading, and per-module API mapping. Isolated on branch ts3-Testing." +todos: + - id: create-branch + content: "Create branch ts3-Testing off current branch; do not touch tsmain / V8 plugin" + status: pending + - id: sdk-license-gate + content: "Phase 0: get TS3 SDK (ClientLib+ServerLib) + evaluation license; build official examples; confirm the API names/signatures flagged VERIFY in this plan against serverlib.h/clientlib.h" + status: pending + - id: serverlib-bootstrap + content: "Phase 1: conan-voice-server bootstrap (initServerLib -> createVirtualServer -> loop -> stopVirtualServer -> destroyServerLib)" + status: pending + - id: sdk-client-poc + content: "Phase 2: custom ClientLib app connects (initClientLib -> spawnNewServerConnectionHandler -> createIdentity -> open devices -> startConnection)" + status: pending + - id: metadata-transport + content: "Phase 2/3: game-data transport via CLIENT_META_DATA (+ flushClientSelfUpdates); confirm server read + change event" + status: pending + - id: routing-poc + content: "Phase 3: server whisper-list routing (ts3server_setClientWhisperList): prove A hears B, not C" + status: pending + - id: threed-voice + content: "Phase 4/5: 3D via ts3client 3D listener/channel attributes fed by server positions" + status: pending + - id: write-en-doc + content: "Mirror this plan into docs/ as the living design doc once Phase 0 confirms API names" + status: pending + - id: keep-v8-untouched + content: "Do not modify REWRITE_PLAN_V8.md / REWRITE_PLAN.md or the V8 plugin on tsmain" + status: pending +isProject: false +--- + +# Conan Voice on TeamSpeak 3 SDK (ClientLib + ServerLib) — Development-Ready Plan + +New product, new branch `ts3-Testing`. The V8 retail plugin on `tsmain` stays intact and is reused for *logic* only. + +> Items marked **[VERIFY]** are exact names/signatures to confirm against `serverlib.h` / `clientlib.h` and the bundled API reference during Phase 0. Everything unmarked is confirmed from TeamSpeak docs/examples. + +--- + +## 0. The two SDKs (why this is a real switch, not a rename) + +| | Client Plugin SDK (today, V8) | TeamSpeak 3 SDK (this plan) | +|---|---|---| +| Header(s) | `ts3_functions.h` (`TS3Functions`) | `clientlib.h` + `serverlib.h` | +| Function family | `ts3plugin_*` callbacks | `ts3client_*` and `ts3server_*` | +| Who hosts voice | retail `ts3server` | **our** `conan-voice-server` process | +| Who runs the client | retail TeamSpeak app loads our DLL | **our** app embeds ClientLib | +| Can decide who hears whom | no (edit local PCM only) | **yes** (`ts3server_setClientWhisperList`) | +| License | free | paid; free evaluation for dev | +| Interop | plugin only talks to retail TS | SDK client only talks to SDK server (`0x0705` otherwise) | + +Proof today is Plugin SDK: `sdk/README.md` line 1 = "TeamSpeak 3 Client Plugin SDK"; repo has `ts3plugin_*`, no `ts3client_*`/`ts3server_*`. + +**Consequence:** the "Conan Voice Client" is a standalone ClientLib app (users run it like the Mumble client), not retail TeamSpeak + plugin. + +--- + +## 1. Component overview + +```mermaid +flowchart LR + subgraph ClientHost [Player PC] + game[Conan Exiles] + prov[IGameDataProvider] + cstate[VoiceClientState] + cadapt[TeamSpeakClientAdapter] + clib[SDK ClientLib] + game --> prov --> cstate --> cadapt --> clib + clib -->|"onEditPlaybackVoiceDataEvent, 3D"| cadapt --> hud[HUD] + end + subgraph ServerHost [Voice server] + slib[SDK ServerLib] + sadapt[TeamSpeakServerAdapter] + pm[PlayerManager] + prox[ProximityEngine] + router[VoiceRouter] + slib --> sadapt --> pm --> prox --> router --> sadapt --> slib + end + clib <-->|"SDK voice + CLIENT_META_DATA"| slib +``` + +--- + +## 2. Server process — exact lifecycle + +File: `server/src/main.c` (+ `server/src/ts_server_adapter.c`). + +Startup order (each step checks the return code with `ts3server_getGlobalErrorMessage` on failure): + +```text +1. load config (server/config/*.ini) -> our Config module +2. start logger -> our Logging module +3. fill ServerLibFunctions (memset 0, assign callbacks) +4. ts3server_initServerLib(&funcs, LogType_FILE|LogType_CONSOLE, logDir) +5. ts3server_createServerKeyPair(...) OR load stored keypair [VERIFY exact name] +6. ts3server_createVirtualServer(port, "0.0.0.0", name, keyPair, maxClients, &serverID) +7. init Voice backend (PlayerManager, ProximityEngine, VoiceRouter) +8. run event loop (routing tick; SDK callbacks fire on SDK threads) +9. shutdown: ts3server_stopVirtualServer(serverID) + ts3server_destroyServerLib() +``` + +Confirmed signatures: + +- `unsigned int ts3server_initServerLib(const struct ServerLibFunctions* funcs, int usedLogTypes, const char* logFileFolder);` +- `unsigned int ts3server_createVirtualServer(unsigned int serverPort, const char* serverIp, const char* serverName, const char* serverKeyPair, unsigned int serverMaxClients, uint64* result);` +- `unsigned int ts3server_stopVirtualServer(uint64 serverID);` +- `unsigned int ts3server_destroyServerLib(void);` +- Routing: `unsigned int ts3server_setClientWhisperList(uint64 serverID, anyID clientID, const uint64* channelIDs, const anyID* clientIDs);` +- Enumerate: `ts3server_getClientList(serverID, anyID** result)`, `ts3server_getChannelList(serverID, uint64** result)`, free with `ts3server_freeMemory(ptr)`. + +Callbacks we register in `ServerLibFunctions` (assign only what we use): + +- `onClientConnected(serverID, clientID, channelID, ...)` **[VERIFY arg list]** -> PlayerManager.add +- `onClientDisconnected(serverID, clientID, channelID, ...)` -> PlayerManager.remove + clear whisper list +- `onClientMoved(serverID, clientID, oldCh, newCh, ...)` +- `onServerTextMessageEvent` / `onChannelTextMessageEvent` -> fallback game-data channel (see 5) +- `onClientStartTalkingEvent` / `onClientStopTalkingEvent` -> HUD "who speaks" hints +- `onUserLoggingMessageEvent` -> our logger +- `onAccountingErrorEvent` -> license/slot errors (must handle for eval license) + +--- + +## 3. Client app — exact lifecycle + +File: `client/src/main.c` (+ `client/src/ts_client_adapter.c`, `client/src/game_provider_*.c`). + +```text +1. fill ClientUIFunctions (callbacks) +2. ts3client_initClientLib(&clientFuncs, NULL, LogType_FILE, NULL, resourcesPath) [VERIFY arg list] +3. ts3client_spawnNewServerConnectionHandler(0, &scHandlerID) +4. identity: load stored, else ts3client_createIdentity(&id) then persist +5. ts3client_openPlaybackDevice(scHandlerID, "", "") // default device + ts3client_openCaptureDevice(scHandlerID, "", "") +6. ts3client_startConnection(scHandlerID, identity, host, port, + nickname, NULL/*default channel*/, "", serverPassword) +7. wait for onConnectStatusChangeEvent == STATUS_CONNECTION_ESTABLISHED +8. loop: push game data (see 5), set 3D (see 6), render HUD +9. shutdown: ts3client_stopConnection(scHandlerID, "leaving") + ts3client_destroyServerConnectionHandler(scHandlerID) + ts3client_destroyClientLib() +``` + +Confirmed: + +- `ts3client_startConnection(scHandlerID, identity, ip, port, nickname, defaultChannelArray, defaultChannelPassword, serverPassword)` +- `ts3client_stopConnection(scHandlerID, quitMessage)` +- `ts3client_getConnectionStatus(scHandlerID, int* result)` +- Callback `onConnectStatusChangeEvent(scHandlerID, newStatus, errorNumber)`; `onServerStopEvent(scHandlerID, shutdownMessage)`. + +ClientLib callbacks we implement: `onConnectStatusChangeEvent`, `onNewChannelEvent`, `onClientMoveEvent`, `onTalkStatusChangeEvent`, `onServerStopEvent`, `onEditPlaybackVoiceDataEvent` (local audio effects: reverb/lowpass reuse from V8), `onCustom3dRolloffCalculationClientEvent` (custom distance curve). **[VERIFY exact callback names in clientlib.h]** + +--- + +## 4. Data flow: connect -> position -> routing -> voice + +```mermaid +sequenceDiagram + participant G as Conan (game) + participant C as Client app (ClientLib) + participant S as Server (ServerLib) + participant R as VoiceRouter + + C->>S: ts3client_startConnection + S-->>C: onConnectStatusChangeEvent(ESTABLISHED) + S->>S: onClientConnected -> PlayerManager.add(clientID) + + loop ~20 Hz + G->>C: IGameDataProvider.getPlayerState(pos,rot,ids) + C->>C: ts3client_setClientSelfVariableAsString(CLIENT_META_DATA, packed) + C->>S: ts3client_flushClientSelfUpdates + S->>S: metadata change -> PlayerManager.updatePos(clientID,pos) + end + + loop ~5-10 Hz (routing tick) + R->>R: spatial hash -> audible set per client + R->>S: ts3server_setClientWhisperList(serverID, A, {}, {B,C}) + R->>S: ts3server_setClientWhisperList(serverID, D, {}, {}) // hears no one + end + + Note over C,S: A speaks -> ClientLib routes A's voice only to B,C (whisper list) + S-->>C: voice packets to B,C only + C->>C: onEditPlaybackVoiceDataEvent -> distance gain/pan/reverb + C->>C: 3D attributes -> spatialization +``` + +Whisper-list routing detail (from TeamSpeak Whisper Lists example): a client with a whisper list bypasses normal channel voice and sends **only** to the listed clients/channels. So the router expresses the full audible matrix as per-speaker client-ID lists. Clearing = `ts3server_setClientWhisperList(serverID, client, NULL, NULL)`. + +Caveat to design around: while a client has an active whisper list, its **normal channel talk is disabled**. Therefore ALL proximity/radio/whisper voice must be expressed through whisper lists (do not mix with channel voice for routed players). + +--- + +## 5. Game-data transport (the previously-missing piece) + +Requirement: client must tell the server its position ~20 Hz **without an external HTTP/REST/WebSocket API** (user constraint). Two in-SDK mechanisms: + +- **Primary: `CLIENT_META_DATA`** (string, up to 4096 bytes, Read/Write). + - Client: `ts3client_setClientSelfVariableAsString(scHandlerID, CLIENT_META_DATA, packedState)` then `ts3client_flushClientSelfUpdates(scHandlerID, NULL)`. + - Server: read with `ts3server_getClientVariableAsString(serverID, clientID, CLIENT_META_DATA, &out)` **[VERIFY server-side getter + whether a change event fires; if no push event, poll on the routing tick]**. + - Payload = compact base64/binary: `playerId,charId,x,y,z,yaw,voiceMode,flags,seq` (reuse V8 CEPOS packing idea, rewritten). +- **Fallback: text messages** `TextMessageTarget_SERVER` -> server `onServerTextMessageEvent`. Heavier; use only for discrete events (RADIO_JOIN, WHISPER_START) if metadata proves too slow. + +Small control protocol (carried over the chosen mechanism): `HELLO, AUTH, AUTH_RESULT, POSITION, VOICE_STATE, RADIO_JOIN/LEAVE, WHISPER_START/STOP, CONFIG, PING/PONG` (from v2 draft, kept). + +--- + +## 6. 3D voice + +Server feeds authoritative positions; **client** applies 3D locally via ClientLib 3D API **[VERIFY exact names]**: + +- Listener (self): `ts3client_systemset3DListenerAttributes(scHandlerID, &posSelf, &forward, &up)` +- Per remote speaker: `ts3client_channelset3DAttributes(scHandlerID, remoteClientID, &posRemote)` (or `ts3client_set3DWaveAttributes`) +- Custom rolloff: implement `onCustom3dRolloffCalculationClientEvent` to match Conan voice ranges. +- Vectors use `TS3_VECTOR {float x,y,z}`. + +We do NOT build a voice engine; TeamSpeak does encode/decode/transport/spatialize. + +--- + +## 7. Module map (server + client) with the APIs each owns + +Server (`server/src/`): +- `ts_server_adapter` — the ONLY file calling `ts3server_*`. Exposes `ITeamSpeakServer` (connectClient/disconnect/getClientState/setWhisperList/getClientMeta/moveClient). +- `player_manager` — `VoicePlayer{tsClientId, conanPlayerId, characterId, pos, voiceMode, authenticated}`; add/remove on connect/disconnect; updatePos from metadata. +- `proximity_engine` — spatial grid (cells), neighbor query, distance -> audible; reuse V8 `proximity_math`/`zone_resolve` logic (rewrite). +- `voice_router` — merges proximity + radio + whisper + global + permissions -> per-client target list -> `ITeamSpeakServer.setWhisperList`; routing cache + hysteresis. +- `auth` — SDK identity -> Conan player mapping via short-lived token. +- `radio_manager`, `whisper_manager`, `permissions` — logical groups. +- `config`, `logging`. + +Client (`client/src/`): +- `ts_client_adapter` — the ONLY file calling `ts3client_*`. Exposes `ITeamSpeakClient`. +- `game_provider` — implements `IGameDataProvider` (file/IPC, ported from Mumble/V8 `pos_file`). +- `voice_client_state`, `audio_effects` (reuse V8 reverb/lowpass in `onEditPlaybackVoiceDataEvent`), `hud`, `config`, `logging`. + +Shared (`shared/`): `protocol/` (message + meta-data packing), `types/` (Vector3, PlayerId, VoiceMode), `config/`. + +--- + +## 8. Threading model + +- **SDK callback threads:** `ts3server_*` / `ts3client_*` callbacks fire on SDK-owned threads. Keep them short; hand off to our queues. (Same discipline as V8's callback-thread rule, but now applied to SDK callbacks.) +- **Routing tick thread (server):** 5-10 Hz; reads player snapshots, computes audible sets, calls `setClientWhisperList`. Never blocks on game I/O. +- **Game provider thread (client):** ~20-30 Hz reads Conan position; publishes snapshot; the flush to metadata happens on a controlled cadence. +- **Audio (client):** `onEditPlaybackVoiceDataEvent` — no locks, snapshot read only (reuse V8 PCM ownership pattern). + +--- + +## 9. Repository layout (branch ts3-Testing) + +```text +conan-voice/ +├── client/ { src/ include/ CMakeLists.txt } +├── server/ { src/ include/ config/ CMakeLists.txt } +├── shared/ { protocol/ types/ config/ } +├── sdk/ { include/ lib/ bin/ examples/ } # examples untouched +├── tests/ { unit/ integration/ load/ } +├── docs/ +└── CMakeLists.txt # SDK path configurable, no C:\Users\... hardcodes +``` + +Kept isolated from `src/` so the V8 DLL build is never disturbed and `tsmain` merges stay clean. + +--- + +## 10. Phases (with concrete API deliverables) + +**Phase 0 — SDK + license + API verification.** Get SDK (ClientLib+ServerLib) + evaluation license; build official ServerLib + ClientLib examples; confirm every **[VERIFY]** name against `serverlib.h`/`clientlib.h`. Exit: examples run; API list frozen. + +**Phase 1 — server bootstrap (Milestone 1).** `main.c` runs steps 1-9 of section 2; start/stop/restart clean. No Conan, no routing. + +**Phase 2 — client PoC + metadata.** ClientLib app connects (section 3); publishes `CLIENT_META_DATA`; server reads it. Two clients talk in one channel. + +**Phase 3 — routing PoC (make-or-break).** `voice_router` sets whisper lists: `A -> {B}` only; then chained `A->B, B->C, C-/->A`. Proves server-authoritative "who hears whom". + +**Phase 4 — Conan integration.** `IGameDataProvider` feeds real position/rotation/ids/voice-state (ported from Mumble/V8). + +**Phase 5 — proximity + 3D.** spatial grid, cache, hysteresis in `proximity_engine`; 3D via section 6. + +**Phase 6 — features.** radio, whisper groups, global, cave reverb (server env state -> client effect), HUD, voice modes, root-channel config. + +**Phase 7 — performance.** 10/25/50/100/250 clients; CPU, RAM, network, routing latency, voice latency, reconnects. + +**Phase 8 — production.** Linux/systemd, auto-restart, logging, monitoring, config validation, crash recovery, security review, **commercial license finalized**. + +--- + +## 11. Definition of done + +- **Milestone 1:** ServerLib up, virtual server created, SDK client connects, two clients pass voice, server changes whisper routing, clean shutdown + restart. +- **v0.1:** Conan position -> proximity voice -> 3D voice; player mapping; voice range; reconnect; basic HUD. +- **v0.2:** radio, whisper, global, root-channel config, cave reverb, HUD colors, permissions. +- **v1.0:** production server+client, 250+ tested, perf documented, monitoring, crash recovery, security review, license cleared, full docs. + +--- + +## 12. Hard gates / risks + +- **License:** paid, negotiated; evaluation only for dev. ([licensing](https://business.teamspeak.com/licensing/)) +- **EOL:** TS3 + TS3 SDK end 2027-12-31 — keep `IVoiceTransport`/`ITeamSpeakServer` seams so transport can be swapped; do not leak SDK calls outside the two adapter files. +- **API-name risk:** some 3D/keypair/metadata-getter names are **[VERIFY]** until Phase 0. +- **Whisper-only routing:** routed clients lose normal channel voice — design accepts this. +- **Position trust:** v1 client-reported (spoofable); v2 game-server-fed. + +--- + +## 13. Open questions (resolved in Phase 0/PoC) + +1. Exact `ServerLibFunctions` / `ClientUIFunctions` callback sets and arg lists. +2. Server-side getter + change event for `CLIENT_META_DATA` (push vs poll). +3. Exact 3D + keypair function signatures. +4. Whisper-list update rate that stays artifact-free at 5-10 Hz. +5. Real client cap under the negotiated license. +6. Position authority (client vs game server) and id binding (Conan/Steam/SDK identity). +7. EOL-2027 successor/exit path. + +--- + +## Guardrails + +- Work only on `ts3-Testing`. Do not touch `REWRITE_PLAN_V8.md` / `REWRITE_PLAN.md` or the V8 plugin. +- Rewrite from V8/Mumble references; never blind-copy. +- Every change gets a `doku/` entry (project rule). +- No code, no branch, no commit until you approve. diff --git a/vergleichs.md b/vergleichs.md new file mode 100644 index 0000000..650c32a --- /dev/null +++ b/vergleichs.md @@ -0,0 +1,218 @@ +# Live Plugin (V8) vs. Planned SDK/ServerLib Architecture + +A detailed, side-by-side comparison of **what the shipping plugin does today** (V8, branch `tsmain`) +versus **what the new plan proposes** (branch `ts3-Testing`, "Option B"): which API each uses, +what stays the same, and what fundamentally changes. + +> [!IMPORTANT] +> Both use "a TeamSpeak SDK" — but **two completely different ones**. +> **Today:** the *Client Plugin SDK* (a guest inside the retail TeamSpeak client). +> **Plan:** the *TeamSpeak 3 SDK* (ClientLib + ServerLib — your own client and your own server). +> Same word, opposite architecture. + +--- + +## 1. At a glance + +| Topic | Live V8 (today) | Plan (Option B) | +|---|---|---| +| SDK used | **Client Plugin SDK** | **TeamSpeak 3 SDK** (ClientLib + ServerLib) | +| Header(s) | `sdk/include/ts3_functions.h` (`TS3Functions`) | `clientlib.h` + `serverlib.h` | +| Function family | `ts3plugin_*` (callbacks), `ts3Functions->*` | `ts3client_*` and `ts3server_*` | +| What "the server" is | Retail `ts3server` (foreign) | **Our own** `conan-voice-server` (ServerLib) | +| What "the client" is | Retail TeamSpeak loads our DLL | **Our own** app embedding ClientLib | +| Who decides "who hears whom" | The client, locally (volume/mute) | The **server** (`ts3server_setClientWhisperList`) | +| Position exchange | Peer-to-peer via `sendPluginCommand` (CEPOS) | Client to server via `CLIENT_META_DATA` | +| Audio control | `onEditPlaybackVoiceDataEvent` (edits samples) | Server-side whisper routing + client-side 3D/PCM | +| 3D sound | `systemset3D*` / `channelset3DAttributes` (in plugin) | Same `ts3client_*3D*` functions (in our app) | +| Needs retail TeamSpeak | **Yes** (users already have it) | **No** — a custom client app is required | +| License | Free | **Paid** (free evaluation for dev only) | +| Lifecycle | Tied to TS3 | TS3 SDK **EOL 2027-12-31** | +| "Who hears whom" reach | Local volume only; everyone still gets packets | Real packet routing (B/C never receive A) | +| Effort | Exists, ships today | Build **both** a client and a server | + +--- + +## 2. The two SDKs — the root of the confusion + +The word "SDK" refers to two different TeamSpeak products. + +### 2a. Today: Client Plugin SDK +- Proof in the repo: `sdk/README.md`, line 1 = "TeamSpeak 3 Client Plugin SDK". +- We ship a **DLL** that the **retail TeamSpeak client loads**. +- We are a guest: we react to events (`ts3plugin_*`) and trigger a few client actions + through the function table `TS3Functions`. +- We **cannot** route voice at the server level. We can only alter incoming voice + **locally** (quieter, pan, filter). + +### 2b. Plan: TeamSpeak 3 SDK (ClientLib + ServerLib) +- A separate download, different libraries, different function families + (`ts3client_*` and `ts3server_*`). +- We **host our own voice server** (ServerLib) and **build our own client** (ClientLib). +- The retail TeamSpeak client is no longer involved — and in fact **cannot** connect to + an SDK server (error `0x0705`, per TeamSpeak docs). + +> [!NOTE] +> This is not an "update" — it is an architecture switch: from "a plugin guest inside +> TeamSpeak" to "a standalone client + server product". + +--- + +## 3. Architecture, side by side + +### Live V8 (today) + +```mermaid +flowchart TD + mod["Conan Exiles mod"] -->|"Pos.txt"| plugin["Our plugin (DLL inside retail TeamSpeak)"] + plugin -->|"sendPluginCommand CEPOS"| peers["Other clients"] + peers -->|"CEPOS packets"| plugin + plugin -->|"onEditPlaybackVoiceDataEvent: gain/pan/filter"| out["Local audio output"] + ts["Retail ts3server"] -.->|"transport + channels only, no game logic"| plugin +``` + +- Each client computes **for itself** how loud it hears the others. +- Positions travel **peer-to-peer** via plugin commands (the retail server just relays them). +- "Who hears whom" is really just a **volume**: voice packets still reach everyone in the + channel; they are only mixed quiet/muted locally. + +### Plan (Option B) + +```mermaid +flowchart TD + game["Conan Exiles"] -->|"local IPC"| client["Conan Voice Client (custom app, ClientLib)"] + client -->|"CLIENT_META_DATA = position"| server["Conan Voice Server (custom process, ServerLib)"] + server -->|"PlayerManager + ProximityEngine + VoiceRouter"| router["setClientWhisperList A to B,C"] + router --> transport["TeamSpeak voice transport (SDK)"] + transport -->|"A's voice only to B and C"| listeners["Clients B, C (D gets nothing)"] +``` + +- The **server** decides centrally who hears whom, and enforces it via whisper lists (real routing). +- Positions go **client to server** (no longer peer-to-peer). + +--- + +## 4. API comparison — concrete functions + +### 4a. Callbacks (events we react to) + +| Purpose | Live V8 (`ts3plugin_*`) | Plan (`ts3client_*` / `ts3server_*`) | +|---|---|---| +| Connection status | `onConnectStatusChangeEvent` | Client: `onConnectStatusChangeEvent` | +| Our own data transfer | `onPluginCommandEvent` (CEPOS/CEDRAIN) | Server: read `CLIENT_META_DATA` change | +| Alter audio | `onEditPlaybackVoiceDataEvent` | Client: `onEditPlaybackVoiceDataEvent` (local effects only) | +| 3D rolloff | `onCustom3dRolloffCalculationClientEvent` | Client: `onCustom3dRolloffCalculationClientEvent` | +| Client moves | `onClientMoveEvent` / `onClientMoveMovedEvent` | Server: `onClientMoved`; Client: `onClientMoveEvent` | +| Channel description (hub) | `onChannelDescriptionUpdateEvent`, `onUpdateChannelEvent`, `onUpdateChannelEditedEvent` | Handled differently (root-channel config) | +| Who is talking | `onTalkStatusChangeEvent` | Server: `onClientStartTalkingEvent` / `onClientStopTalkingEvent` | +| Connect/disconnect (server) | not available to us | Server: `onClientConnected` / `onClientDisconnected` | + +*Evidence: `src/ts/entry/ts3_exports.h`, `src/ts/entry/ts3_entry.c`.* + +### 4b. Functions we actively call (what we trigger) + +| Purpose | Live V8 (`TS3Functions`, `g_ts3.*`) | Plan (SDK) | +|---|---|---| +| Send our own data | `sendPluginCommand("CEPOS:...")` | Client: `setClientSelfVariableAsString(CLIENT_META_DATA)` + `flushClientSelfUpdates` | +| Wakeup / tick | `sendPluginCommand("CEDRAIN:1")` | Own event loop in the server | +| Unmute clients | `requestUnmuteClientsTemporary` | Not needed (routing is server-side) | +| Per-client volume | `setClientVolumeModifier` | Mainly via whisper routing; fine gain optionally local | +| Move into channel (hub) | `requestClientMove` | Server: `ts3server_*` channel ops | +| 3D global | `systemset3DSettings` | Client: `ts3client_systemset3DSettings` | +| 3D listener (self) | `systemset3DListenerAttributes` | Client: `ts3client_systemset3DListenerAttributes` | +| 3D per speaker | `channelset3DAttributes` | Client: `ts3client_channelset3DAttributes` | +| **Who hears whom (routing)** | **does not exist** (local volume only) | **Server: `ts3server_setClientWhisperList`** | +| Start server | not applicable | `ts3server_initServerLib` then `ts3server_createVirtualServer` | +| Stop server | not applicable | `ts3server_stopVirtualServer` then `ts3server_destroyServerLib` | +| Start client | (retail client does this) | `ts3client_initClientLib` then `spawnNewServerConnectionHandler` then `startConnection` | + +*Evidence: `src/ts/adapter/ts3_adapter.c` (around lines 427, 653-690, 737-768).* + +> [!TIP] +> The 3D functions (`systemset3DListenerAttributes`, `channelset3DAttributes`, +> `systemset3DSettings`) are named almost identically in **both** SDKs, so that part is +> largely portable. The **big** difference is routing: `ts3server_setClientWhisperList` +> exists only in the ServerLib. + +--- + +## 5. Data flow in detail + +### 5a. Position exchange + +| | Live V8 | Plan | +|---|---|---| +| Source | `Pos.txt` (game mod) via `pos_file.c` | `IGameDataProvider` (ported from Mumble/V8) | +| Packing | CEPOS, 56 bytes, base64 | compact binary/base64 in `CLIENT_META_DATA` (max 4096 B) | +| Path | Client to **all** clients (peer-to-peer, `sendPluginCommand`) | Client to **server** (`CLIENT_META_DATA`) | +| Who computes distance | **each client itself** | **the server** (authoritative) | +| Trust | Client reports its position (spoofable) | Client reports position (spoofable) — server is still the authority for routing/permissions | + +### 5b. "Who hears whom" + +- **Live V8:** purely client-side. All voice packets arrive; the plugin only makes remote + speakers **quieter or muted** (`onEditPlaybackVoiceDataEvent` + `setClientVolumeModifier`). + Bandwidth: every packet always flows. +- **Plan:** server-side. The `VoiceRouter` builds a target list per speaker and calls + `ts3server_setClientWhisperList(A, ..., {B,C})`. Anyone not on the list never receives A's + voice at all. + +> [!WARNING] +> While a whisper list is active, that client's normal channel talk path is disabled. +> Therefore **everything** must be routed through whisper lists for routed players. + +### 5c. Audio effects (cave reverb, lowpass) + +- **Live V8:** applied directly on the samples in `onEditPlaybackVoiceDataEvent` + (`ts3_proximity_audio.c`, lock-free snapshot pattern). +- **Plan:** the same logic stays **client-side** (the custom app also has + `onEditPlaybackVoiceDataEvent`), driven by a server-provided environment state + (NORMAL/CAVE/...). So the effect code is **reusable**. + +--- + +## 6. Operations, deployment, license + +| Topic | Live V8 | Plan | +|---|---|---| +| User install | DLL into `%AppData%\TS3Client\plugins\` | Install/run a custom client app | +| Server operation | none (retail server is enough) | Own process (Linux/systemd), monitoring, crash recovery | +| Build | MSVC + MinGW cross, gcc unit tests | CMake for client + server + shared | +| License | free (Plugin SDK) | paid, case by case; evaluation free for dev only | +| Lifecycle | as long as TS3 lives | TS3 SDK **EOL 2027-12-31** — keep an `IVoiceTransport` seam as exit | +| Scale | callback thread + PCM, ~200 players target | server-side routing, but slot cap per license | + +--- + +## 7. What stays the same / is reusable + +- **Proximity math** (`proximity_math`, `zone_resolve`) → port into the ServerLib engine (rewrite, don't copy). +- **Audio effects** (reverb/lowpass in `ts3_proximity_audio.c`) → usable almost as-is, client-side. +- **3D calls** (`systemset3D*`, `channelset3DAttributes`) → nearly identical names. +- **Position source** (`pos_file.c`, path detection) → wrap as `IGameDataProvider`. +- **HUD / voice modes / config / hub-parser ideas** → carry over as logic. + +## 8. What is fundamentally different + +1. **Two new processes** (a custom client **and** a custom server) instead of one DLL. +2. **Routing moves from client to server** (whisper lists instead of local volume). +3. **Position path reverses**: no longer peer-to-peer, but client to server. +4. **Retail TeamSpeak disappears** — users need our app. +5. **License and EOL** become new concerns. + +--- + +## 9. Conclusion + +- Live V8 is a **plugin inside someone else's TeamSpeak**: easy to distribute, free, but + "who hears whom" is only a **local volume** (all packets always flow). +- The plan is a **standalone voice product** on the TeamSpeak **server** library: **real** + server-side routing, at the cost of building a client and server, a paid license, and a + 2027 EOL date. +- Reusable above all: **proximity math, audio effects, and 3D calls**. New and risky: + **ServerLib hosting, whisper routing, data transport (`CLIENT_META_DATA`), and licensing**. + +> [!IMPORTANT] +> The plan only makes sense once the PoC (phases 0-3) proves that the ServerLib starts +> cleanly and whisper routing "A hears B, not C" actually works. Before that, no Conan code +> is worth writing.