Files
AyeZeeBB c27d6cb053 Word filter: fewer false flags, servers' own word lists; protocol 49
The chat filter matched short listed words anywhere inside a longer one, so
entries like "gger", "spik", "crip" and "coon" in the backend's lists blocked
"bigger", "spike", "script" and "raccoon".

- A listed word of four or five letters now matches inside a longer word only
  when the rest of it is what goes around a swear word ("motherfuckers",
  "bullshit", "shithead", "shitty", "xxfuckxx"). Words of six letters or more
  still match anywhere.
- An ordinary word that equals a listed phrase without its spaces
  ("the rapist" / "therapist") no longer matches.
- bad_words.txt is gone: the filter has no words until the backend's lists
  arrive.

Dedicated servers:
- "use_global_word_filter" (default true) turns the backend's lists off for a
  server. Console: words global on|off.
- data/words_filtered.txt and data/words_blocked.txt hold a server's own
  words, read at start and by "words reload". Blocked words follow
  word_warnings. Filtered words are sent to players (new packet, kind 33),
  whose games star them for players with the chat filter on and drop them on
  leaving.

Multiplayer outfits: a player whose mods add or remove skater or board slots
no longer makes everyone look like the local player. A peer's items are put
in this game's slots by slot hash; slots the peer lacks stay empty and items
in slots this game lacks are left out.
2026-10-11 13:17:28 -05:00

219 lines
13 KiB
C++

#include "Extension/Multiplayer/Net/protocol.h"
#include "Extension/Multiplayer/Net/delta_codec.h"
#include "Extension/Multiplayer/Session/room.h"
#include "Engine/Game/Multiplayer/voice_settings.h"
#include "Engine/Game/Input/voice_input.h"
#include "Extension/Multiplayer/Voice/native_voice_pcm.h"
#include <cmath>
#include <iostream>
#include <limits>
#include <stdexcept>
using namespace dingosdk;
using namespace dingosdk::multiplayer;
namespace {
void check(bool condition, const char *message) {
if (!condition) throw std::runtime_error(message);
}
Packet voice_packet(std::size_t size) {
Packet packet;
packet.kind = PacketKind::voice;
packet.source = 11; packet.epoch = 22; packet.session = 33; packet.map = 44;
packet.world = 3; packet.sequence = 8; packet.time_us = 10000;
packet.voice.bytes.resize(size);
for (std::size_t i = 0; i < size; ++i) packet.voice.bytes[i] = static_cast<std::uint8_t>(i * 17);
return packet;
}
void rejected(const Packet &packet) {
try { (void)encode(packet); } catch (const std::invalid_argument &) { return; }
throw std::runtime_error("Invalid voice was encoded");
}
}
int main() try {
{
NativeVoicePcm pcm;
std::array<std::int16_t, 500> source;
for (std::size_t n = 0; n < source.size(); ++n) source[n] = static_cast<std::int16_t>(static_cast<int>(n) * 50 - 12000);
const auto bytes = std::span(reinterpret_cast<const std::uint8_t *>(source.data()), sizeof(source));
check(!pcm.append(bytes.first(3)), "Odd PCM byte count accepted");
check(pcm.append(bytes.first(240)) && pcm.append(bytes.subspan(240)), "Split PCM packet rejected");
check(pcm.samples() == source.size(), "Native framing added silence between packets");
NativeVoiceBlock block;
check(pcm.pop(block, 1.f) && std::equal(block.begin(), block.end(), source.begin()), "Native sample ordering changed");
check(!pcm.pop(block, 1.f), "Partial native block submitted before completion");
pcm.flush();
check(pcm.pop(block, 1.f) && std::equal(source.begin() + 320, source.end(), block.begin()), "Native tail samples lost");
check(std::all_of(block.begin() + 180, block.end(), [](auto v) { return v == 0; }), "Native tail not zero padded");
std::vector<std::uint8_t> full(native_voice_rate);
check(pcm.append(full) && !pcm.append(bytes.first(2)), "Native voice queue is unbounded");
check(pcm.pop(block, 1.f), "Full native queue could not drain");
pcm = {};
const std::array<std::int16_t, 4> peaks{20000,-20000,32767,-32768};
check(pcm.append({reinterpret_cast<const std::uint8_t *>(peaks.data()), sizeof(peaks)}), "Gain input rejected");
pcm.flush();
check(pcm.pop(block, 10.f) && block[0] == 32767 && block[1] == -32768, "Native gain wrapped PCM peaks");
check(pcm.samples() == 0, "Native PCM remained after drain");
}
for (const auto size : {std::size_t{1}, std::size_t{1024}, max_voice_bytes}) {
auto packet = voice_packet(size);
packet.voice.gain = 4.5f;
packet.voice.policy_revision = 9;
const auto bytes = encode(packet);
const auto decoded = decode(bytes);
check(decoded && decoded->kind == PacketKind::voice && decoded->voice.bytes == packet.voice.bytes &&
decoded->voice.distance == packet.voice.distance && decoded->world == packet.world &&
decoded->source == packet.source && decoded->epoch == packet.epoch &&
decoded->voice.gain == packet.voice.gain && decoded->voice.policy_revision == 9, "Voice packet round trip failed");
for (std::size_t length = 0; length < bytes.size(); ++length)
check(!decode(std::span(bytes).first(length)), "Truncated voice packet accepted");
auto extra = bytes; extra.push_back(0);
check(!decode(extra), "Trailing voice data accepted");
}
auto packet = voice_packet(128);
DeltaSender sender;
DeltaReceiver receiver;
auto first = sender.prepare(packet);
check(!first.establishes_baseline(), "Voice depends on a reliable pose baseline");
sender.sent(packet, std::move(first));
++packet.sequence;
const auto next = sender.prepare(packet);
bool missing{};
const auto received = receiver.receive(next.bytes, missing, packet.world);
check(received && !missing && received->voice.bytes == packet.voice.bytes, "Lost voice chunk prevents subsequent playback");
const Member member{packet.source, packet.epoch, {}};
check(routed_source(packet, member, packet.source, true, 55) &&
routed_source(packet, member, 55, false, 55) &&
!routed_source(packet, member, 66, false, 55), "Voice sender routing permits impersonation");
ReceiveBudget link_budget;
for (int i = 0; i < 2 * 120 + 50 + 20; ++i)
check(link_budget.accept(1000000, 32), "120 TPS plus voice trips the link budget");
packet.voice.distance = 0;
check(decode(encode(packet))->voice.distance == 0.f, "Session-wide voice was rejected");
packet.voice.distance = 300.f;
check(decode(encode(packet))->voice.distance == 300.f, "A long hearing distance was rejected");
for (const float radius : {-1.f, 1.f, max_hearing_distance + 1.f, std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
packet.voice.distance = radius; rejected(packet);
}
rejected(voice_packet(0)); rejected(voice_packet(max_voice_bytes + 1));
packet = voice_packet(5); packet.source = 0; rejected(packet);
packet = voice_packet(5); packet.map = 0; rejected(packet);
packet = voice_packet(5); packet.world = 0; rejected(packet);
auto invalid_radius = encode(voice_packet(5));
invalid_radius[64] = 0; invalid_radius[65] = 0; invalid_radius[66] = 0x80; invalid_radius[67] = 0x7f;
check(!decode(invalid_radius), "Non-finite received radius accepted");
auto invalid_size = encode(voice_packet(5));
invalid_size[76] = 255; invalid_size[77] = 255;
check(!decode(invalid_size), "Invalid received voice length accepted");
for (const float gain : {-1.f, 10.1f, std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
packet = voice_packet(5); packet.voice.gain = gain; rejected(packet);
}
packet = voice_packet(5); packet.voice.policy_revision = 0; rejected(packet);
auto invalid_gain = encode(voice_packet(5));
invalid_gain[68] = 0; invalid_gain[69] = 0; invalid_gain[70] = 0x80; invalid_gain[71] = 0x7f;
check(!decode(invalid_gain), "Non-finite received microphone gain accepted");
packet = voice_packet(5);
VoicePolicy policy;
check(policy.accepts(packet.voice), "Initial policy rejected current voice");
policy.allowed = false; ++policy.revision;
check(!policy.accepts(packet.voice), "Host disable accepted voice");
packet.voice.policy_revision = policy.revision;
check(!policy.accepts(packet.voice), "Matching revision bypassed host disable");
policy.allowed = true; ++policy.revision;
check(!policy.accepts(packet.voice), "Re-enabling voice replayed stale audio");
packet.voice.policy_revision = policy.revision;
check(policy.accepts(packet.voice), "Re-enabled voice rejected fresh audio");
packet.kind = PacketKind::roster;
packet.members = {{76561198000000001ULL, 22, "Host"}, {76561198000000002ULL, 66, "Guest"}};
for (const bool allowed : {false, true}) {
packet.voice_policy = {allowed, 34};
const auto encoded = encode(packet);
const auto decoded = decode(encoded);
check(decoded && decoded->voice_policy == packet.voice_policy, "Host voice policy lost in roster");
check(decoded && decoded->voice_range == packet.voice_range, "Host voice range lost in roster");
// The roster ends: voice allowed (1), policy revision (4), voice range (2), guest noclip / No Bail (1),
// then what a dedicated server adds, here none: polls (1), custom votes (1), announcement (4).
const auto end = encoded.size() - 6;
for (std::size_t i = end - 8; i < encoded.size(); ++i)
check(!decode(std::span(encoded).first(i)), "Truncated voice policy accepted");
auto invalid = encoded; invalid[end - 8] = 2;
check(!decode(invalid), "Invalid host voice toggle accepted");
invalid = encoded; invalid[end - 1] = 128;
check(!decode(invalid), "Invalid guest noclip / No Bail byte accepted");
invalid = encoded;
for (std::size_t i = end - 7; i < end - 3; ++i) invalid[i] = 0;
check(!decode(invalid), "Zero host voice revision accepted");
invalid = encoded; invalid[end - 3] = 10; invalid[end - 2] = 0;
check(!decode(invalid), "Voice range below the minimum accepted");
}
const std::array<float, 3> origin{};
auto stereo = voice_stereo(origin, {1, 0, 0}, {-10, 0, 0});
check(stereo[0] == 1.f && stereo[1] == 0.f, "Player on camera left is not in left ear");
stereo = voice_stereo(origin, {1, 0, 0}, {10, 0, 0});
check(stereo[0] == 0.f && stereo[1] == 1.f, "Player on camera right is not in right ear");
stereo = voice_stereo(origin, {-1, 0, 0}, {10, 0, 0});
check(stereo[0] == 1.f && stereo[1] == 0.f, "Voice did not follow camera rotation");
stereo = voice_stereo(origin, {}, {10, 0, 0});
check(stereo[0] == stereo[1], "Missing camera should center voice instead of using body heading");
stereo = voice_stereo(origin, {1, 0, 0}, {0, 0, -10});
check(stereo[0] == stereo[1], "Player in front is not centered");
stereo = voice_stereo(origin, {1, 0, 0}, {std::numeric_limits<float>::quiet_NaN(), 0, 0});
check(std::isfinite(stereo[0]) && stereo[0] == stereo[1], "Invalid spatial audio matrix");
check(voice_gain(origin, origin, 35) == 1.f, "Nearby voice is attenuated");
check(voice_gain(origin, {35, 0, 0}, 35) == 0.f, "Voice outside the radius is audible");
check(voice_gain(origin, {200, 0, 0}, 0) == 1.f, "Session-wide voice is distance limited");
check(voice_gain(origin, {17.5f, 0, 0}, 35) > .25f, "Voice fades out too early");
const auto edge = voice_gain(origin, {34, 0, 0}, 35);
check(edge > 0.f && edge < .05f, "Voice does not trail off near the edge");
float previous = 1.f;
for (int i = 0; i <= 100; ++i) {
const auto gain = voice_gain(origin, {static_cast<float>(i), 0, 0}, 35);
check(gain >= 0 && gain <= previous, "Voice attenuation is not monotonic");
previous = gain;
}
check(voice_gain(origin, {std::numeric_limits<float>::quiet_NaN(), 0, 0}, 35) == 0.f,
"Non-finite player position is audible");
VoiceBudget budget;
for (int i = 0; i < 80; ++i) check(budget.accept(1000000, 32), "Normal voice cadence rejected");
check(!budget.accept(1000000, 32) && budget.accept(2000000, 32), "Voice rate limit failed");
budget = {};
for (int i = 0; i < 2; ++i) check(budget.accept(1000000, max_voice_bytes), "Voice byte budget rejected early");
check(!budget.accept(1000000, 1), "Voice byte budget exceeded");
VoiceSettings settings;
check(settings.valid() && !settings.enabled && !settings.open_mic && settings.push_to_talk == 'V', "Unsafe microphone default");
settings.volume = settings.microphone = max_voice_volume;
settings.controller_combo = 0x300;
check(settings.valid(), "Supported gain or controller combo rejected");
VoicePushToTalk ptt;
ControllerInput pad{true, 0, 1};
check(!ptt.update(settings, true, pad, false), "Held key transmitted before release");
check(!ptt.update(settings, false, pad, false), "Released key transmitted");
check(ptt.update(settings, true, pad, false) && ptt.update(settings, true, pad, false), "Keyboard PTT does not stay active while held");
check(!ptt.update(settings, true, pad, true) && !ptt.update(settings, true, pad, false), "Menu/focus gate failed to disarm keyboard");
check(!ptt.update(settings, false, pad, false), "Idle controller transmitted");
pad.buttons = 0x100;
check(!ptt.update(settings, false, pad, false), "Partial controller combo transmitted");
pad.buttons = 0x300;
check(ptt.update(settings, false, pad, false) && ptt.update(settings, false, pad, false), "Controller PTT does not stay active while held");
pad.device = 2;
check(!ptt.update(settings, false, pad, false), "Controller reconnect transmitted held combo");
pad.buttons = 0;
check(!ptt.update(settings, false, pad, false), "Released controller transmitted");
pad.buttons = 0x300;
check(ptt.update(settings, false, pad, false), "Reconnected controller could not re-arm");
settings.controller_combo = 0x100;
check(!ptt.update(settings, false, pad, false), "Controller rebind transmitted immediately");
settings.push_to_talk = 'B';
check(!ptt.update(settings, true, {}, false), "Keyboard rebind transmitted immediately");
settings.push_to_talk = 0;
check(settings.valid(), "Cleared keyboard binding was rejected");
settings.controller_combo = 0x40000;
check(!settings.valid(), "Invalid controller bits accepted");
settings.controller_combo = 0;
settings.microphone = 11;
check(!settings.valid(), "Unsupported microphone volume accepted");
settings.microphone = 1;
settings.volume = std::numeric_limits<float>::quiet_NaN();
check(!settings.valid(), "Non-finite voice volume accepted");
std::cout << "Voice codec, host policy, gain limits, PTT release gates and proximity checks passed.\n";
} catch (const std::exception &error) { std::cerr << error.what() << '\n'; return 1; }