From 1ae9b88b01630c5dbf95a1e043db5a56e57186b7 Mon Sep 17 00:00:00 2001 From: SchattenundLicht Date: Mon, 17 Aug 2026 18:13:54 +0200 Subject: [PATCH] vergleichs.md aktualisiert --- vergleichs.md | 576 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 414 insertions(+), 162 deletions(-) diff --git a/vergleichs.md b/vergleichs.md index 650c32a..9e70109 100644 --- a/vergleichs.md +++ b/vergleichs.md @@ -1,218 +1,470 @@ -# Live Plugin (V8) vs. Planned SDK/ServerLib Architecture +# 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. +A **development-ready** side-by-side comparison of: -> [!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. +| Document side | What it is | +|---|---| +| **Live V8** | Shipping Conan Exiles proximity plugin (`tsmain`): a DLL loaded by the **retail** TeamSpeak 3 client | +| **Plan (Option B)** | New product on branch `ts3-Testing`: custom **ClientLib** app + custom **ServerLib** voice server | + +This file answers: *which API does each use, in what order, who sends data to whom, and what actually changes.* --- -## 1. At a glance +## Table of contents + +1. [One-sentence difference](#1-one-sentence-difference) +2. [At-a-glance table](#2-at-a-glance-table) +3. [The two SDKs explained](#3-the-two-sdks-explained) +4. [Architecture diagrams](#4-architecture-diagrams) +5. [Lifecycle: boot order (Live V8)](#5-lifecycle-boot-order-live-v8) +6. [Lifecycle: boot order (Plan)](#6-lifecycle-boot-order-plan) +7. [API map: every callback](#7-api-map-every-callback) +8. [API map: every active call we make](#8-api-map-every-active-call-we-make) +9. [Data flow: position](#9-data-flow-position) +10. [Data flow: who hears whom](#10-data-flow-who-hears-whom) +11. [Data flow: 3D and audio effects](#11-data-flow-3d-and-audio-effects) +12. [Threading models](#12-threading-models) +13. [Module map](#13-module-map) +14. [Operations, license, scale](#14-operations-license-scale) +15. [What is reusable vs rewritten](#15-what-is-reusable-vs-rewritten) +16. [Conclusion](#16-conclusion) + +--- + +## 1. One-sentence difference + +> **Live V8** is a guest DLL inside retail TeamSpeak that edits voice **locally**. +> **The plan** is our own TeamSpeak-compatible voice stack where a **server** decides who receives whose packets. + +Same English word "SDK". Two different TeamSpeak products. Not an upgrade — an architecture switch. + +--- + +## 2. At-a-glance table | 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 | +| Product shape | One DLL (`conan_exiles.dll`) | Client app + server process | +| SDK product | **Client Plugin SDK** (free) | **TeamSpeak 3 SDK** ClientLib + ServerLib (paid) | +| Headers | `sdk/include/ts3_functions.h` | `clientlib.h`, `serverlib.h` | +| Function prefixes | `ts3plugin_*`, `TS3Functions.*` | `ts3client_*`, `ts3server_*` | +| Voice host | Foreign retail `ts3server` | Our `conan-voice-server` | +| User runs | TeamSpeak 3 Client + our plugin | Our Conan Voice Client app | +| Who hears whom | Client PCM mute/gain | Server `ts3server_setClientWhisperList` | +| Position path | Peer-to-peer `sendPluginCommand("CEPOS:…")` | Client → server `CLIENT_META_DATA` | +| Packet bandwidth | All channel members always receive voice | Only audible targets receive voice | +| License | Free | Paid (eval free for development) | +| EOL risk | Tied to TS3 client | TS3 SDK EOL **2027-12-31** | +| Branch | `tsmain` | `ts3-Testing` (isolated) | --- -## 2. The two SDKs — the root of the confusion +## 3. The two SDKs explained -The word "SDK" refers to two different TeamSpeak products. +### 3.1 Live V8 — Client Plugin SDK -### 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). +Evidence in this repo: -### 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). +```1:3:sdk/README.md +# TeamSpeak 3 Client Plugin SDK -> [!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 +Welcome to the official helper repository for creating native plugins for the TeamSpeak 3 Client. ``` -- 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. +- TeamSpeak loads our DLL and calls exported `ts3plugin_*` functions. +- We call back into TeamSpeak through `struct TS3Functions` filled by `ts3plugin_setFunctionPointers`. +- We **cannot** create a virtual server, set server-side whisper routing, or replace TeamSpeak. +- Proximity = edit PCM samples in `ts3plugin_onEditPlaybackVoiceDataEvent`. -### Plan (Option B) +There are **zero** `ts3client_*` / `ts3server_*` symbols in this repository. -```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)"] +### 3.2 Plan — TeamSpeak 3 SDK (ClientLib + ServerLib) + +- Separate commercial download (not the Plugin SDK folder in this repo). +- We link **ServerLib** into `conan-voice-server` and **ClientLib** into our custom client. +- Retail TeamSpeak **cannot** connect to an SDK ServerLib server (community-confirmed error `0x0705`). +- Users install **our** client app (similar to how Mumble works), not TeamSpeak + a plugin. + +```text +Plugin SDK (today) TeamSpeak 3 SDK (plan) +───────────────── ────────────────────── +ts3plugin_* ts3client_* (client app) +TS3Functions.* ts3server_* (our server) +retail TS loads DLL we host ServerLib ourselves +local PCM mix server whisper-list routing ``` -- 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. Architecture diagrams + +### 4.1 Live V8 + +```mermaid +sequenceDiagram + participant Game as Conan Mod + participant Plugin as Plugin DLL + participant Peer as Other Plugin + participant PCM as Playback PCM + participant Retail as Retail ts3server + + Game->>Plugin: Pos.txt (x,y,z,yaw) + Plugin->>Retail: sendPluginCommand("CEPOS:...") + Retail->>Peer: relay plugin command + Peer->>Peer: player_table_put + proximity_math + Retail->>Plugin: voice packets from Peer + Plugin->>PCM: onEditPlaybackVoiceDataEvent gain/pan/filter +``` + +### 4.2 Plan (Option B) + +```mermaid +sequenceDiagram + participant Game as Conan + participant Client as Conan Voice Client + participant Server as Conan Voice Server + participant Router as VoiceRouter + + Game->>Client: IGameDataProvider position + Client->>Server: CLIENT_META_DATA + flushClientSelfUpdates + Server->>Server: PlayerManager.updatePos + Router->>Router: spatial hash -> audible set + Router->>Server: ts3server_setClientWhisperList(A,{B,C}) + Note over Client,Server: A speaks - only B and C receive packets + Client->>Client: 3D attributes + optional local effects +``` --- -## 4. API comparison — concrete functions +## 5. Lifecycle: boot order (Live V8) -### 4a. Callbacks (events we react to) +Retail TeamSpeak owns the process. Our plugin only hooks in. -| 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` | +```text +1. TeamSpeak loads conan_exiles.dll +2. ts3plugin_name / version / apiVersion / author / description +3. ts3plugin_setFunctionPointers(TS3Functions) -> ts3_adapter_set_functions +4. ts3plugin_registerPluginID(id) -> needed for sendPluginCommand +5. ts3plugin_init() +6. onConnectStatusChangeEvent(STATUS_CONNECTION_ESTABLISHED) +7. Runtime loop (callback thread + CEDRAIN + PCM): + - pos watcher reads Pos.txt + - CEPOS send/receive via onPluginCommandEvent + - proximity recompute on callback thread + - onEditPlaybackVoiceDataEvent applies mix + - systemset3DListenerAttributes / channelset3DAttributes +8. ts3plugin_shutdown() -> ts3_adapter_shutdown +``` -*Evidence: `src/ts/entry/ts3_exports.h`, `src/ts/entry/ts3_entry.c`.* +Key files: -### 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. +| Step | File | +|---|---| +| Exports | `src/ts/entry/ts3_exports.h`, `ts3_entry.c` | +| TS API wrapper | `src/ts/adapter/ts3_adapter.c` | +| CEPOS | `src/ts/proximity/ts3_cepos.c` | +| PCM mix | `src/ts/proximity/ts3_proximity_audio.c` | +| 3D | `src/ts/proximity/ts3_3d.c` | +| Position file | `src/core/mod_file/pos_file.c` | --- -## 5. Data flow in detail +## 6. Lifecycle: boot order (Plan) -### 5a. Position exchange +### 6.1 Server process (`conan-voice-server`) -| | Live V8 | Plan | +```text +1. Load config + start logger +2. memset(&ServerLibFunctions, 0, sizeof(...)) +3. Assign callbacks (onClientConnected, onClientDisconnected, ...) +4. ts3server_initServerLib(&funcs, LogType_FILE|LogType_CONSOLE, logDir) +5. Create/load server keypair [VERIFY exact name] +6. ts3server_createVirtualServer(port, ip, name, keyPair, maxClients, &serverID) +7. Init PlayerManager, ProximityEngine, VoiceRouter +8. Event loop + routing tick (5-10 Hz) +9. Shutdown: + ts3server_stopVirtualServer(serverID) + ts3server_destroyServerLib() +``` + +Confirmed server APIs: + +| Function | Role | +|---|---| +| `ts3server_initServerLib` | Start ServerLib with callback table | +| `ts3server_createVirtualServer` | Create the virtual server | +| `ts3server_stopVirtualServer` | Stop one virtual server | +| `ts3server_destroyServerLib` | Tear down ServerLib | +| `ts3server_setClientWhisperList` | **Core routing API** | +| `ts3server_getClientList` | Enumerate clients | +| `ts3server_getChannelList` | Enumerate channels | +| `ts3server_freeMemory` | Free SDK-allocated arrays | + +### 6.2 Client app (`conan-voice-client`) + +```text +1. Fill ClientUIFunctions callbacks +2. ts3client_initClientLib(...) +3. ts3client_spawnNewServerConnectionHandler(0, &scHandlerID) +4. ts3client_createIdentity (or load stored identity) +5. ts3client_openPlaybackDevice / openCaptureDevice +6. ts3client_startConnection(scHandlerID, identity, host, port, nick, ...) +7. Wait onConnectStatusChangeEvent == STATUS_CONNECTION_ESTABLISHED +8. Loop: + - read game position + - setClientSelfVariableAsString(CLIENT_META_DATA, packed) + - flushClientSelfUpdates + - apply 3D listener/speaker attributes +9. Shutdown: + ts3client_stopConnection + ts3client_destroyServerConnectionHandler + ts3client_destroyClientLib +``` + +--- + +## 7. API map: every callback + +### Live V8 — `ts3plugin_*` (exported) + +From `src/ts/entry/ts3_exports.h`: + +| Callback | What V8 does with it | +|---|---| +| `ts3plugin_init` / `shutdown` | Start/stop plugin subsystems | +| `ts3plugin_setFunctionPointers` | Store `TS3Functions` into adapter | +| `ts3plugin_registerPluginID` | Required for `sendPluginCommand` | +| `onConnectStatusChangeEvent` | Adopt active connection / reset caches | +| `onPluginCommandEvent` | Receive CEPOS / CEDRAIN / plugin traffic | +| `onEditPlaybackVoiceDataEvent` | **Primary proximity mix** (gain/pan/filter) | +| `onCustom3dRolloffCalculationClientEvent` | Custom distance curve for 3D | +| `onClientMoveEvent` / `onClientMoveMovedEvent` | Hub ↔ ingame channel tracking | +| `onChannelDescriptionUpdateEvent` | Hub settings from root channel description | +| `onUpdateChannelEvent` / `onUpdateChannelEditedEvent` | Channel metadata refresh | +| `onTalkStatusChangeEvent` | Talk indicator / HUD hints | +| `onHotkeyEvent` | Voice-mode hotkeys | + +### Plan — ClientLib + ServerLib callbacks + +| Side | Callback | Role | |---|---|---| -| 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 | +| Client | `onConnectStatusChangeEvent` | Connection lifecycle | +| Client | `onEditPlaybackVoiceDataEvent` | Local effects only (reverb/lowpass) | +| Client | `onCustom3dRolloffCalculationClientEvent` | Distance curve | +| Client | `onClientMoveEvent` | Channel visibility | +| Client | `onTalkStatusChangeEvent` | HUD | +| Server | `onClientConnected` | `PlayerManager.add` | +| Server | `onClientDisconnected` | remove + clear whisper list | +| Server | `onClientMoved` | channel tracking | +| Server | `onClientStartTalkingEvent` / `Stop` | diagnostics / HUD hints | +| Server | `onAccountingErrorEvent` | license/slot errors | -### 5b. "Who hears whom" +Names marked **VERIFY** in the plan must be confirmed against `serverlib.h` / `clientlib.h` in Phase 0. -- **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. +--- + +## 8. API map: every active call we make + +### Live V8 — through `TS3Functions` (`g_ts3.*` in `ts3_adapter.c`) + +| Call | Wrapper | Why | +|---|---|---| +| `sendPluginCommand` | `ts3_send_plugin_command_server` | CEPOS positions + CEDRAIN wakeup | +| `requestUnmuteClientsTemporary` | `ts3_unmute_clients_for_pcm` | PCM hook only receives unmuted clients | +| `setClientVolumeModifier` | (inside unmute path) | Reset volume modifiers | +| `requestClientMove` | `ts3_request_client_move` | Hub ↔ ingame | +| `requestChannelDescription` | `ts3_request_channel_description` | Hub config | +| `getChannelDescription` | `ts3_get_channel_description` | Parse hub settings | +| `systemset3DSettings` | `ts3_set_3d_settings` | 3D global scale | +| `systemset3DListenerAttributes` | `ts3_set_3d_listener` | Listener pose | +| `channelset3DAttributes` | `ts3_set_3d_client` | Per-speaker 3D position | +| get channel/client lists, nicknames, printMessage, logMessage | various | channel manage / UI / logging | + +**Missing in Live V8:** any server API that stops packets to non-audible clients. + +### Plan — SDK calls + +| Call | Side | Why | +|---|---|---| +| `ts3server_initServerLib` / `createVirtualServer` / `stop` / `destroy` | Server | Host voice | +| `ts3server_setClientWhisperList` | Server | **Who hears whom** | +| `ts3server_getClientList` / `getChannelList` / `freeMemory` | Server | Enumerate + free | +| `ts3client_initClientLib` / `spawnNewServerConnectionHandler` | Client | Host client | +| `ts3client_createIdentity` / `startConnection` / `stopConnection` | Client | Connect | +| `ts3client_setClientSelfVariableAsString(CLIENT_META_DATA)` | Client | Send position/state | +| `ts3client_flushClientSelfUpdates` | Client | Push metadata to server | +| `ts3client_systemset3DListenerAttributes` | Client | Listener 3D | +| `ts3client_channelset3DAttributes` | Client | Speaker 3D | +| `ts3client_openPlaybackDevice` / `openCaptureDevice` | Client | Audio devices | + +--- + +## 9. Data flow: position + +| Step | Live V8 | Plan | +|---|---|---| +| 1. Game source | Mod writes `Pos.txt` | Same idea via `IGameDataProvider` | +| 2. Read | `pos_file.c` watcher (~30 ms) | Game provider thread (~20–30 Hz) | +| 3. Pack | CEPOS 56-byte base64 | Compact binary/base64 in `CLIENT_META_DATA` (≤4096 B) | +| 4. Send | `sendPluginCommand("CEPOS:…")` to **all clients** | `setClientSelfVariableAsString` + `flushClientSelfUpdates` to **server** | +| 5. Receive | `onPluginCommandEvent` → `player_table_put` | Server reads metadata → `PlayerManager.updatePos` | +| 6. Distance math | **Each client** (`proximity_math`) | **Server** (`ProximityEngine`) | +| 7. Trust | Client-reported (spoofable) | Client-reported (spoofable); server still owns routing/permissions | + +```text +Live V8: A ──CEPOS──▶ B,C,D (everyone learns A's position) +Plan: A ──META───▶ Server ──whisper lists──▶ only who should hear A +``` + +--- + +## 10. Data flow: who hears whom + +### Live V8 (local presentation only) + +```text +Retail server delivers everyone's voice in the channel + │ + ▼ +onEditPlaybackVoiceDataEvent(clientID, samples[]) + │ + ├─ distance far → multiply samples by ~0 (mute) + ├─ distance near → gain + pan + lowpass/reverb + └─ must unmute via requestUnmuteClientsTemporary + otherwise PCM hook gets nothing +``` + +Result: **bandwidth still paid for every speaker**. Hearing is a local volume illusion. + +### Plan (real network routing) + +```text +VoiceRouter tick (5-10 Hz) + for each speaker S: + audible = proximity ∪ radio ∪ whisper ∪ global ∩ permissions + if audible changed: + ts3server_setClientWhisperList(serverID, S, NULL, audibleClientIds) + # or clear with (NULL, NULL) +``` + +Result: non-audible clients **never receive** S's voice packets. > [!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**. +> With an active whisper list, normal channel talk for that client is disabled. +> Therefore **all** proximity/radio/whisper/global must be expressed as whisper lists. --- -## 6. Operations, deployment, license +## 11. Data flow: 3D and audio effects + +| Concern | Live V8 | Plan | +|---|---|---| +| Listener pose | `systemset3DListenerAttributes` | `ts3client_systemset3DListenerAttributes` | +| Speaker pose | `channelset3DAttributes` | `ts3client_channelset3DAttributes` | +| Global 3D | `systemset3DSettings` | `ts3client_systemset3DSettings` | +| Custom curve | `onCustom3dRolloffCalculationClientEvent` | Same idea on ClientLib | +| Cave reverb / lowpass | PCM path in `ts3_proximity_audio.c` | Still client PCM; server may send env state (CAVE/NORMAL) | + +**Portable:** 3D call pattern and effect DSP. +**Not portable:** using PCM mute as the *primary* "who hears whom" mechanism. + +--- + +## 12. Threading models + +### Live V8 (strict) + +| Thread | May do | Must not | +|---|---|---| +| TS callback | All `TS3Functions` / CEDRAIN drain | Block | +| Wakeup | Only `sendPluginCommand("CEDRAIN:1")` | Other TS API | +| PCM audio | Read snapshots; apply gain/pan | TS API, locks | +| Pos watcher / UI | Set flags + wakeup | TS API | + +### Plan + +| Thread | May do | Must not | +|---|---|---| +| SDK server callbacks | Short handoff into our queues | Heavy proximity math inline | +| Routing tick (5–10 Hz) | Compute audible sets; call `setClientWhisperList` | Game file I/O | +| SDK client callbacks | Connection/PCM/3D | Block | +| Game provider (~20 Hz) | Read Conan; publish snapshot | Direct SDK calls without adapter | + +Same discipline as V8: **one adapter owns all SDK calls**. + +--- + +## 13. Module map + +| Concern | Live V8 files | Plan modules | +|---|---|---| +| Entry / lifecycle | `ts3_entry.c` | `client/main.c`, `server/main.c` | +| TS API wrapper | `ts3_adapter.c` | `TeamSpeakClientAdapter`, `TeamSpeakServerAdapter` | +| Positions | `pos_file.c`, `ts3_cepos.c` | `IGameDataProvider`, `CLIENT_META_DATA` | +| Player table | `player_table.c` | `PlayerManager` (server) | +| Proximity math | `proximity_math.c`, `zone_resolve.c` | `ProximityEngine` (server) | +| Hearability | PCM + volume modifier | `VoiceRouter` + whisper lists | +| 3D | `ts3_3d.c` | ClientLib 3D calls | +| Hub config | `hub_parser.c`, `ts3_server_profile.c` | root-channel / server config | +| Voice modes | `voice_modes.c` | client state + server ranges | +| UI / HUD | `ui_main.c`, overlay | client HUD | + +--- + +## 14. Operations, license, scale | 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 | +| User install | DLL → `%AppData%\TS3Client\plugins\` | Install custom client | +| Server ops | None (use any retail TS server) | Own Linux process, systemd, monitoring | +| Build | MSVC + MinGW cross + gcc tests | CMake: client + server + shared | +| License | Free Plugin SDK | Paid SDK; evaluation for PoC | +| Scale target | ~200 players (512 table cap) | License slot cap + routing cost | +| EOL | TS3 client lifetime | **SDK EOL 2027-12-31** | --- -## 7. What stays the same / is reusable +## 15. What is reusable vs rewritten -- **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. +### Reuse (rewrite into new adapters — do not blind-copy) -## 8. What is fundamentally different +- Proximity math / zones (`proximity_math`, `zone_resolve`) +- PCM effects (reverb/lowpass patterns from `ts3_proximity_audio.c`) +- 3D call sequencing ideas from `ts3_3d.c` +- Position acquisition ideas from `pos_file.c` / Mumble reference +- HUD / voice-mode UX concepts -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. +### Must be new + +- Entire ServerLib host process +- Entire ClientLib application shell +- Whisper-list router as the hearability authority +- `CLIENT_META_DATA` (or equivalent) transport replacing CEPOS P2P +- License/accounting handling +- Deployment (systemd, crash recovery) + +### Explicitly abandoned for Option B + +- Depending on retail TeamSpeak client +- CEPOS peer-to-peer as the primary position bus +- Local PCM mute as the primary routing mechanism --- -## 9. Conclusion +## 16. 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**. +| | Live V8 | Plan | +|---|---|---| +| Strength | Ships today; free; users already have TeamSpeak | True server routing; central authority; bandwidth win | +| Weakness | Everyone still receives packets; client can spoof position; no server game logic | New client+server product; paid license; SDK EOL 2027; retail TS gone | +| Right next step | Continue maintaining on `tsmain` | On `ts3-Testing`: prove ServerLib boot + whisper routing **before** Conan code | > [!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. +> Do not write Conan integration for Option B until Phase 0–3 succeed: +> SDK examples build → server starts/stops → two clients talk → +> `ts3server_setClientWhisperList` proves **A hears B, not C**. + +--- + +*Related living plan:* [`.cursor/plans/voice_architecture_plan_ed3d8830.plan.md`](.cursor/plans/voice_architecture_plan_ed3d8830.plan.md)