# Live Plugin (V8) vs Planned SDK/ServerLib Architecture A **development-ready** side-by-side comparison of: | 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.* --- ## 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) | |---|---|---| | 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) | --- ## 3. The two SDKs explained ### 3.1 Live V8 — Client Plugin SDK Evidence in this repo: ```1:3:sdk/README.md # TeamSpeak 3 Client Plugin SDK Welcome to the official helper repository for creating native plugins for the TeamSpeak 3 Client. ``` - 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`. There are **zero** `ts3client_*` / `ts3server_*` symbols in this repository. ### 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 ``` --- ## 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 ``` --- ## 5. Lifecycle: boot order (Live V8) Retail TeamSpeak owns the process. Our plugin only hooks in. ```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 ``` Key files: | 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` | --- ## 6. Lifecycle: boot order (Plan) ### 6.1 Server process (`conan-voice-server`) ```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 | |---|---|---| | 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 | Names marked **VERIFY** in the plan must be confirmed against `serverlib.h` / `clientlib.h` in Phase 0. --- ## 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] > 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. --- ## 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 → `%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** | --- ## 15. What is reusable vs rewritten ### Reuse (rewrite into new adapters — do not blind-copy) - 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 ### 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 --- ## 16. Conclusion | | 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] > 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)