From dbc862291fb9c003a32083e952262456e69528a2 Mon Sep 17 00:00:00 2001
From: dank_meme01 <42031238+dankmeme01@users.noreply.github.com>
Date: Thu, 7 Dec 2023 14:26:11 +0100
Subject: [PATCH] stuff and some tidying

---
 .gitignore                                    |   7 +-
 CMakeLists.txt                                |  15 +-
 server/central/src/config.rs                  |  10 +-
 server/game-derive/src/lib.rs                 |   2 +-
 server/game/Cargo.toml                        |  14 ++
 server/game/benchmarks/bench.rs               | 133 +++++++++++++++++
 server/game/src/data/bytebufferext.rs         |  72 +++------
 server/game/src/data/types/common.rs          |  56 +++++--
 server/game/src/lib.rs                        |  17 +++
 server/game/src/main.rs                       |   4 +-
 server/game/src/managers/player.rs            |  27 ++--
 .../game/src/server_thread/handlers/game.rs   |  37 +----
 server/game/src/server_thread/handlers/mod.rs |   2 +-
 server/game/src/server_thread/mod.rs          |  66 ++++++---
 server/game/tests/test.rs                     | 138 ++++++++++++++++++
 server/shared/src/lib.rs                      |   4 +-
 src/audio/all.hpp                             |   2 +-
 src/audio/audio_frame.hpp                     |   2 +-
 src/audio/audio_manager.cpp                   |  36 ++---
 src/audio/audio_manager.hpp                   |   4 +-
 src/audio/audio_sample_queue.hpp              |   2 +-
 src/audio/audio_stream.cpp                    |   5 +-
 src/audio/audio_stream.hpp                    |   2 +-
 src/audio/opus_codec.cpp                      |   2 +-
 src/audio/opus_codec.hpp                      |   2 +-
 src/audio/voice_playback_manager.cpp          |   2 +-
 src/audio/voice_playback_manager.hpp          |   4 +-
 src/config.hpp                                |   2 +-
 src/crypto/base_box.hpp                       |   2 +-
 src/crypto/box.cpp                            |  12 +-
 src/crypto/box.hpp                            |  12 +-
 src/crypto/secret_box.cpp                     |  12 +-
 src/crypto/secret_box.hpp                     |  12 +-
 src/defs.hpp                                  |   2 +-
 src/defs/net.hpp                              |   2 +-
 src/main.cpp                                  |   2 +-
 src/managers/account_manager.cpp              |   1 -
 src/managers/central_server_manager.cpp       |   4 +-
 src/managers/central_server_manager.hpp       |   2 +-
 src/managers/error_queues.hpp                 |   2 +-
 src/managers/game_server_manager.hpp          |   2 +-
 src/managers/settings.hpp                     |   2 +-
 src/net/network_manager.cpp                   |   4 +-
 src/net/udp_socket.cpp                        |   2 +-
 src/ui/error_check_node.cpp                   |   2 +-
 src/ui/error_check_node.hpp                   |   4 +-
 src/ui/hooks/menu_layer.hpp                   |   2 +-
 src/ui/hooks/play_layer.hpp                   |  11 +-
 src/ui/menu/main/globed_menu_layer.cpp        |  15 +-
 src/ui/menu/main/signup_layer.cpp             |   2 +-
 src/ui/menu/main/signup_popup.cpp             |   6 +-
 .../menu/server_switcher/add_server_popup.cpp |   2 +-
 src/util/crypto.cpp                           |  10 +-
 src/util/data.hpp                             |   2 +-
 src/util/debugging.cpp                        |   8 +-
 src/util/math.cpp                             |  16 +-
 src/util/math.hpp                             |  12 ++
 src/util/net.cpp                              |   4 +-
 src/util/rng.cpp                              |   5 +
 src/util/rng.hpp                              |   3 +
 src/util/sync.hpp                             |   2 +-
 src/util/time.cpp                             |   2 +-
 src/util/time.hpp                             |   2 +-
 src/util/ui.cpp                               |   2 +-
 src/util/ui.hpp                               |   4 +-
 65 files changed, 602 insertions(+), 258 deletions(-)
 create mode 100644 server/game/benchmarks/bench.rs
 create mode 100644 server/game/src/lib.rs
 create mode 100644 server/game/tests/test.rs

diff --git a/.gitignore b/.gitignore
index 377c6dd8..b1ecf72d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,4 +62,9 @@ mod-a.json
 server/bench-client
 server/target
 server/central-conf.json
-server/Cargo.lock
\ No newline at end of file
+server/Cargo.lock
+server/game/flamegraph.svg
+server/game/perf.data
+server/game/perf.data.old
+server/game/test-flamegraph.sh
+server/game/test-bench.sh
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2d0444e1..5742026c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,7 +21,7 @@ file(GLOB_RECURSE SOURCES
 # Include winsock
 if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
     add_compile_definitions(WIN32_LEAN_AND_MEAN=1) # geode moment
-    add_definitions(/FI"winsock2.h")
+    add_definitions(/FI"WinSock2.h")
 endif()
 
 # i am crying so hard right now
@@ -29,13 +29,20 @@ if (CMAKE_HOST_SYSTEM MATCHES "Linux" AND CMAKE_SYSTEM_NAME STREQUAL "Windows")
     add_compile_options("-march=skylake")
 endif()
 
-# disable iterator debugging if building in debug
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-    add_compile_definitions(_HAS_ITERATOR_DEBUGGING=0)
+# enable exceptions on android
+if (ANDROID)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
 endif()
 
 add_library(${PROJECT_NAME} SHARED ${SOURCES})
 
+# enable extra warnings
+if (CMAKE_HOST_SYSTEM STREQUAL "Windows")
+else()
+    # i hate this language okay?
+    target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++11-compat -Wno-c++14-compat -Wno-c++17-compat -Wno-old-style-cast -Wno-implicit-int-float-conversion -Wno-global-constructors -Wno-pre-c++20-compat-pedantic -Wno-exit-time-destructors -Wno-reserved-identifier -Wno-reserved-macro-identifier -Wno-dollar-in-identifier-extension -Wno-ctad-maybe-unsupported -Wno-unsafe-buffer-usage -Wno-newline-eof -Wno-shadow -Wno-inconsistent-missing-destructor-override -Wno-float-conversion -Wno-shorten-64-to-32 -Wno-sign-conversion -Wno-suggest-destructor-override -Wno-suggest-override -Wno-overloaded-virtual -Wno-unused-parameter -Wno-undefined-func-template -Wno-non-virtual-dtor -Wno-sign-compare -Wno-header-hygiene)
+endif()
+
 if (NOT DEFINED ENV{GEODE_SDK})
     message(FATAL_ERROR "Unable to find Geode SDK! Please define GEODE_SDK environment variable to point to Geode")
 else()
diff --git a/server/central/src/config.rs b/server/central/src/config.rs
index b71c935e..961d7534 100644
--- a/server/central/src/config.rs
+++ b/server/central/src/config.rs
@@ -1,5 +1,5 @@
 use std::{
-    collections::{HashMap, HashSet},
+    collections::HashMap,
     fs::{File, OpenOptions},
     path::Path,
 };
@@ -47,8 +47,8 @@ fn default_userlist_mode() -> UserlistMode {
     UserlistMode::None
 }
 
-fn default_userlist() -> HashSet<i32> {
-    HashSet::new()
+fn default_userlist() -> Vec<i32> {
+    Vec::new()
 }
 
 fn default_tps() -> u32 {
@@ -121,9 +121,9 @@ pub struct ServerConfig {
     #[serde(default = "default_userlist_mode")]
     pub userlist_mode: UserlistMode,
     #[serde(default = "default_userlist")]
-    pub userlist: HashSet<i32>,
+    pub userlist: Vec<i32>,
     #[serde(default = "default_userlist")]
-    pub no_chat_list: HashSet<i32>,
+    pub no_chat_list: Vec<i32>,
 
     // game stuff
     #[serde(default = "default_tps")]
diff --git a/server/game-derive/src/lib.rs b/server/game-derive/src/lib.rs
index 18e56bd2..3014e50c 100644
--- a/server/game-derive/src/lib.rs
+++ b/server/game-derive/src/lib.rs
@@ -270,7 +270,7 @@ struct PacketAttributes {
     encrypted: bool,
 }
 
-/// Implements `Packet`, `PacketMetadata` and the function `header() -> PacketHeader` for the given struct.
+/// Implements `Packet`, `PacketMetadata` and the function `const fn header() -> PacketHeader` for the given struct.
 /// You must also pass additional attributes with `#[packet]`, specifically packet ID and whether the packet should be encrypted.
 /// Example:
 /// ```rust
diff --git a/server/game/Cargo.toml b/server/game/Cargo.toml
index b4857133..ed3ac8cf 100644
--- a/server/game/Cargo.toml
+++ b/server/game/Cargo.toml
@@ -14,9 +14,23 @@ anyhow = "1.0.75"
 array-init = "2.1.0"
 bytebuffer = "2.2.0"
 crypto_box = { version = "0.9.1", features = ["std", "chacha20"] }
+nohash-hasher = "0.2.0"
 parking_lot = "0.12.1"
 reqwest = "0.11.22"
 rustc-hash = "1.1.0"
 serde = { version = "1.0.193", features = ["serde_derive"] }
 serde_json = "1.0.108"
 tokio = { version = "1.34.0", features = ["full"] }
+
+[dev-dependencies]
+criterion = "0.5.1"
+rand = "0.8.5"
+
+[[bench]]
+name = "globed-bench"
+path = "benchmarks/bench.rs"
+harness = false
+
+[[test]]
+name = "globed-tests"
+path = "tests/test.rs"
diff --git a/server/game/benchmarks/bench.rs b/server/game/benchmarks/bench.rs
new file mode 100644
index 00000000..b9504308
--- /dev/null
+++ b/server/game/benchmarks/bench.rs
@@ -0,0 +1,133 @@
+#![allow(clippy::wildcard_imports)]
+use bytebuffer::{ByteBuffer, ByteReader};
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use globed_game_server::{data::*, managers::PlayerManager};
+use rand::RngCore;
+
+fn buffers(c: &mut Criterion) {
+    let data = PlayerAccountData {
+        account_id: 234_234_234,
+        name: FastString::from_str("hit his is my name"),
+        icons: PlayerIconData::default(),
+        special_user_data: Some(SpecialUserData {
+            name_color: Color3B { r: 10, g: 100, b: 200 },
+        }),
+    };
+
+    c.bench_function("alloca-byte-buffer", |b| {
+        b.iter(black_box(|| {
+            alloca::with_alloca(PlayerAccountData::ENCODED_SIZE * 64, |data_| {
+                let stackarray = unsafe {
+                    let ptr = data_.as_mut_ptr().cast::<u8>();
+                    std::slice::from_raw_parts_mut(ptr, std::mem::size_of_val(data_))
+                };
+
+                let mut buf = FastByteBuffer::new(stackarray);
+                for _ in 0..64 {
+                    buf.write_value(&data);
+                }
+
+                // let mut buf = ByteReader::from_bytes(stackarray);
+                // for _ in 0..64 {
+                //     assert_eq!(buf.read_value::<PlayerAccountData>().unwrap().account_id, data.account_id);
+                // }
+            });
+        }));
+    });
+
+    c.bench_function("fast-byte-buffer", |b| {
+        b.iter(black_box(|| {
+            let mut stackarray = [0u8; PlayerAccountData::ENCODED_SIZE * 64];
+            let mut buf = FastByteBuffer::new(&mut stackarray);
+            for _ in 0..64 {
+                buf.write_value(&data);
+            }
+
+            // let mut buf = ByteReader::from_bytes(&stackarray);
+            // for _ in 0..64 {
+            //     assert_eq!(buf.read_value::<PlayerAccountData>().unwrap().account_id, data.account_id);
+            // }
+        }));
+    });
+
+    c.bench_function("slow-byte-buffer", |b| {
+        b.iter(black_box(|| {
+            let mut buffer = ByteBuffer::new();
+            for _ in 0..64 {
+                buffer.write_value(&data);
+            }
+        }));
+    });
+}
+
+fn structs(c: &mut Criterion) {
+    let mut data = [0u8; 2048];
+    rand::thread_rng().fill_bytes(&mut data);
+
+    c.bench_function("encode-audio-frame", |b| {
+        b.iter(black_box(|| {
+            let data = FastEncodedAudioFrame {
+                data: data.to_vec().into(),
+            };
+            let mut stack_array = [0u8; 3000 * 8];
+            let mut buf = FastByteBuffer::new(&mut stack_array);
+            for _ in 0..8 {
+                buf.write_value(&data);
+            }
+
+            let written_data = buf.as_bytes();
+
+            for _ in 0..128 {
+                let mut reader = ByteReader::from_bytes(written_data);
+
+                let abc = reader.read_value::<FastEncodedAudioFrame>().unwrap();
+                assert_eq!(abc.data.len(), written_data.len());
+            }
+        }));
+    });
+}
+
+fn managers(c: &mut Criterion) {
+    c.bench_function("player-manager", |b| {
+        b.iter(black_box(|| {
+            let mut manager = PlayerManager::new();
+
+            for level_id in 0..100 {
+                for account_id in 0..10 {
+                    manager.add_to_level(level_id, level_id * 10 + account_id);
+                    manager.set_player_data(level_id * 10 + account_id, &PlayerData {});
+                }
+            }
+
+            let mut total_players = 0;
+            for i in 0..100 {
+                let count = manager.get_player_count_on_level(i).unwrap_or(0);
+                assert_eq!(count, 10);
+
+                let total = manager.for_each_player_on_level(
+                    i,
+                    |_, _, p| {
+                        *p += 1;
+                        true
+                    },
+                    &mut total_players,
+                );
+
+                assert_eq!(total, count);
+            }
+
+            assert_eq!(total_players, 1000);
+
+            for level_id in 0..100 {
+                for account_id in 0..10 {
+                    manager.remove_from_level(level_id, level_id * 10 + account_id);
+                    manager.remove_player(level_id * 10 + account_id);
+                }
+            }
+        }));
+    });
+}
+
+criterion_group!(benches, buffers, structs, managers);
+// criterion_group!(benches, structs);
+criterion_main!(benches);
diff --git a/server/game/src/data/bytebufferext.rs b/server/game/src/data/bytebufferext.rs
index ec4ab06f..63d3e0bd 100644
--- a/server/game/src/data/bytebufferext.rs
+++ b/server/game/src/data/bytebufferext.rs
@@ -1,9 +1,6 @@
 use std::fmt::Display;
 
-use crate::data::{
-    packets::{PacketHeader, PacketMetadata},
-    types::cocos,
-};
+use crate::data::packets::{PacketHeader, PacketMetadata};
 use bytebuffer::{ByteBuffer, ByteReader};
 
 #[derive(Debug)]
@@ -132,18 +129,6 @@ macro_rules! size_calc_impl {
     };
 }
 
-/// Simple way of getting total encoded size of given primitives.
-///
-/// Example usage:
-/// ```rust
-/// assert_eq!(16, size_of_primitives!(u64, i32, i16, i8, bool));
-/// ```
-macro_rules! size_of_primitives {
-    ($($t:ty),+ $(,)?) => {{
-        0 $(+ std::mem::size_of::<$t>())*
-    }};
-}
-
 /// Simple way of getting total (maximum) encoded size of given types that implement `Encodable` and `KnownSize`
 ///
 /// Example usage:
@@ -159,7 +144,6 @@ macro_rules! size_of_types {
 pub(crate) use decode_impl;
 pub(crate) use encode_impl;
 pub(crate) use size_calc_impl;
-pub(crate) use size_of_primitives;
 pub(crate) use size_of_types;
 
 /* ByteBuffer extensions */
@@ -187,10 +171,6 @@ pub trait ByteBufferExtWrite {
     fn write_value_vec<T: Encodable>(&mut self, val: &[T]);
 
     fn write_packet_header<T: PacketMetadata>(&mut self);
-
-    fn write_color3(&mut self, val: cocos::Color3B);
-    fn write_color4(&mut self, val: cocos::Color4B);
-    fn write_point(&mut self, val: cocos::Point);
 }
 
 pub trait ByteBufferExtRead {
@@ -214,20 +194,18 @@ pub trait ByteBufferExtRead {
     fn read_value_vec<T: Decodable>(&mut self) -> DecodeResult<Vec<T>>;
 
     fn read_packet_header(&mut self) -> DecodeResult<PacketHeader>;
-
-    fn read_color3(&mut self) -> DecodeResult<cocos::Color3B>;
-    fn read_color4(&mut self) -> DecodeResult<cocos::Color4B>;
-    fn read_point(&mut self) -> DecodeResult<cocos::Point>;
 }
 
 /// Buffer for encoding that does zero heap allocation but also has limited functionality.
 /// It will panic on writes if there isn't enough space.
+/// On average, is at least 5x faster than a regular `ByteBuffer`.
 pub struct FastByteBuffer<'a> {
     pos: usize,
     len: usize,
     data: &'a mut [u8],
 }
 
+#[allow(clippy::inline_always)]
 impl<'a> FastByteBuffer<'a> {
     /// Create a new `FastByteBuffer` given this mutable slice
     pub fn new(src: &'a mut [u8]) -> Self {
@@ -242,71 +220,88 @@ impl<'a> FastByteBuffer<'a> {
         Self { pos: 0, len, data: src }
     }
 
+    #[inline(always)]
     pub fn write_u8(&mut self, val: u8) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_u16(&mut self, val: u16) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_u32(&mut self, val: u32) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_u64(&mut self, val: u64) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_i8(&mut self, val: i8) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_i16(&mut self, val: i16) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_i32(&mut self, val: i32) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_i64(&mut self, val: i64) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_f32(&mut self, val: f32) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_f64(&mut self, val: f64) {
         self.internal_write(&val.to_be_bytes());
     }
 
+    #[inline(always)]
     pub fn write_bytes(&mut self, data: &[u8]) {
         self.internal_write(data);
     }
 
+    #[inline(always)]
     pub fn write_string(&mut self, val: &str) {
         self.write_u32(val.len() as u32);
         self.write_bytes(val.as_bytes());
     }
 
+    #[inline(always)]
     pub fn as_bytes(&'a mut self) -> &'a [u8] {
         &self.data[..self.len]
     }
 
+    #[inline(always)]
     pub fn len(&self) -> usize {
         self.len
     }
 
+    #[inline(always)]
     pub fn set_pos(&mut self, pos: usize) {
         self.pos = pos;
     }
 
+    #[inline(always)]
     pub fn is_empty(&self) -> bool {
         self.len == 0
     }
 
+    #[inline(always)]
     pub fn capacity(&self) -> usize {
         self.data.len()
     }
@@ -373,18 +368,6 @@ macro_rules! impl_extwrite {
         fn write_packet_header<T: PacketMetadata>(&mut self) {
             self.write_value(&PacketHeader::from_packet::<T>());
         }
-
-        fn write_color3(&mut self, val: cocos::Color3B) {
-            self.write_value(&val);
-        }
-
-        fn write_color4(&mut self, val: cocos::Color4B) {
-            self.write_value(&val);
-        }
-
-        fn write_point(&mut self, val: cocos::Point) {
-            self.write_value(&val);
-        }
     };
 }
 
@@ -403,7 +386,8 @@ macro_rules! impl_extread {
             let remainder = self.len() - self.get_rpos();
             let mut data = Vec::with_capacity(remainder);
 
-            // safety: we don't allow to read any unitialized memory, as we trust the return value of `Read::read`
+            // safety: we use `Vec::set_len` appropriately so the caller won't be able to read uninitialized data.
+            // we could avoid unsafe and use io::Read::read_to_end here, but that is significantly slower.
             unsafe {
                 let ptr = data.as_mut_ptr();
                 let mut slice = std::slice::from_raw_parts_mut(ptr, remainder);
@@ -442,18 +426,6 @@ macro_rules! impl_extread {
         fn read_packet_header(&mut self) -> DecodeResult<PacketHeader> {
             self.read_value()
         }
-
-        fn read_color3(&mut self) -> DecodeResult<cocos::Color3B> {
-            self.read_value()
-        }
-
-        fn read_color4(&mut self) -> DecodeResult<cocos::Color4B> {
-            self.read_value()
-        }
-
-        fn read_point(&mut self) -> DecodeResult<cocos::Point> {
-            self.read_value()
-        }
     };
 }
 
diff --git a/server/game/src/data/types/common.rs b/server/game/src/data/types/common.rs
index b1bb8e08..95976834 100644
--- a/server/game/src/data/types/common.rs
+++ b/server/game/src/data/types/common.rs
@@ -9,13 +9,33 @@ use crate::data::bytebufferext::*;
 
 macro_rules! impl_primitive {
     ($typ:ty,$read:ident,$write:ident) => {
-        encode_impl!($typ, buf, self, {
-            buf.$write(*self);
-        });
-
-        decode_impl!($typ, buf, { buf.$read().map_err(|e| e.into()) });
-
-        size_calc_impl!($typ, size_of_primitives!(Self));
+        impl crate::data::Encodable for $typ {
+            #[inline(always)]
+            fn encode(&self, buf: &mut bytebuffer::ByteBuffer) {
+                buf.$write(*self);
+            }
+
+            #[inline(always)]
+            fn encode_fast(&self, buf: &mut crate::data::FastByteBuffer) {
+                buf.$write(*self);
+            }
+        }
+
+        impl crate::data::Decodable for $typ {
+            #[inline(always)]
+            fn decode(buf: &mut bytebuffer::ByteBuffer) -> crate::data::DecodeResult<Self> {
+                buf.$read().map_err(|e| e.into())
+            }
+
+            #[inline(always)]
+            fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> crate::data::DecodeResult<Self> {
+                buf.$read().map_err(|e| e.into())
+            }
+        }
+
+        impl crate::data::KnownSize for $typ {
+            const ENCODED_SIZE: usize = std::mem::size_of::<$typ>();
+        }
     };
 }
 
@@ -167,12 +187,12 @@ decode_impl!(PublicKey, buf, {
     Ok(Self::from_bytes(key))
 });
 
-/* RemainderBytes - wrapper around Vec<u8> that decodes with `buf.read_remaining_bytes()` and encodes with `buf.write_bytes()` */
+/* RemainderBytes - wrapper around Box<[u8]> that decodes with `buf.read_remaining_bytes()` and encodes with `buf.write_bytes()` */
 
 #[derive(Clone)]
 #[repr(transparent)]
 pub struct RemainderBytes {
-    data: Vec<u8>,
+    data: Box<[u8]>,
 }
 
 encode_impl!(RemainderBytes, buf, self, {
@@ -181,13 +201,27 @@ encode_impl!(RemainderBytes, buf, self, {
 
 decode_impl!(RemainderBytes, buf, {
     Ok(Self {
-        data: buf.read_remaining_bytes()?,
+        data: buf.read_remaining_bytes()?.into(),
     })
 });
 
 impl Deref for RemainderBytes {
-    type Target = Vec<u8>;
+    type Target = [u8];
     fn deref(&self) -> &Self::Target {
         &self.data
     }
 }
+
+impl From<Vec<u8>> for RemainderBytes {
+    fn from(value: Vec<u8>) -> Self {
+        Self {
+            data: value.into_boxed_slice(),
+        }
+    }
+}
+
+impl From<Box<[u8]>> for RemainderBytes {
+    fn from(value: Box<[u8]>) -> Self {
+        Self { data: value }
+    }
+}
diff --git a/server/game/src/lib.rs b/server/game/src/lib.rs
new file mode 100644
index 00000000..3d165be7
--- /dev/null
+++ b/server/game/src/lib.rs
@@ -0,0 +1,17 @@
+#![feature(sync_unsafe_cell)]
+#![allow(
+    clippy::must_use_candidate,
+    clippy::module_name_repetitions,
+    clippy::cast_possible_truncation,
+    clippy::missing_errors_doc,
+    clippy::missing_panics_doc,
+    clippy::wildcard_imports,
+    clippy::missing_safety_doc
+)]
+
+pub mod data;
+pub mod managers;
+pub mod server;
+pub mod server_thread;
+pub mod state;
+pub mod util;
diff --git a/server/game/src/main.rs b/server/game/src/main.rs
index 18482c87..68c9f20b 100644
--- a/server/game/src/main.rs
+++ b/server/game/src/main.rs
@@ -17,7 +17,7 @@
 )]
 
 use std::{
-    collections::{HashMap, HashSet},
+    collections::HashMap,
     error::Error,
     net::{IpAddr, Ipv4Addr, SocketAddr},
 };
@@ -161,7 +161,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
         warn!("Starting in standalone mode, authentication is disabled");
         GameServerBootData {
             protocol: PROTOCOL_VERSION,
-            no_chat: HashSet::new(),
+            no_chat: Vec::new(),
             special_users: HashMap::new(),
             tps: 30,
         }
diff --git a/server/game/src/managers/player.rs b/server/game/src/managers/player.rs
index cfc4aef3..d1d0ddee 100644
--- a/server/game/src/managers/player.rs
+++ b/server/game/src/managers/player.rs
@@ -1,4 +1,4 @@
-use rustc_hash::{FxHashMap, FxHashSet};
+use nohash_hasher::IntMap;
 
 use crate::data::{
     types::{AssociatedPlayerData, PlayerData},
@@ -12,15 +12,15 @@ pub struct PlayerEntry {
 }
 
 pub struct PlayerManager {
-    players: FxHashMap<i32, PlayerEntry>,   // player id : associated data
-    levels: FxHashMap<i32, FxHashSet<i32>>, // level id : [player id]
+    players: IntMap<i32, PlayerEntry>, // player id : associated data
+    levels: IntMap<i32, Vec<i32>>,     // level id : [player id]
 }
 
 impl PlayerManager {
     pub fn new() -> Self {
         Self {
-            players: FxHashMap::default(),
-            levels: FxHashMap::default(),
+            players: IntMap::default(),
+            levels: IntMap::default(),
         }
     }
 
@@ -57,16 +57,16 @@ impl PlayerManager {
     }
 
     /// get a reference to a list of account IDs of players on a level given its ID
-    pub fn get_level(&self, level_id: i32) -> Option<&FxHashSet<i32>> {
+    pub fn get_level(&self, level_id: i32) -> Option<&Vec<i32>> {
         self.levels.get(&level_id)
     }
 
     /// get the amount of players on a level given its ID
     pub fn get_player_count_on_level(&self, level_id: i32) -> Option<usize> {
-        self.levels.get(&level_id).map(FxHashSet::len)
+        self.levels.get(&level_id).map(Vec::len)
     }
 
-    /// run a function `f` on each player on a level given its ID, with additional data (wink wink it's always gonna be `&mut FastByteBuffer`)
+    /// run a function `f` on each player on a level given its ID, with possibility to pass additional data
     pub fn for_each_player_on_level<F, A>(&self, level_id: i32, f: F, additional: &mut A) -> usize
     where
         F: Fn(&PlayerEntry, usize, &mut A) -> bool,
@@ -82,14 +82,19 @@ impl PlayerManager {
 
     /// add a player to a level given a level ID and an account ID
     pub fn add_to_level(&mut self, level_id: i32, account_id: i32) {
-        let players = self.levels.entry(level_id).or_default();
-        players.insert(account_id);
+        let players = self.levels.entry(level_id).or_insert_with(|| Vec::with_capacity(128));
+        if !players.contains(&account_id) {
+            players.push(account_id);
+        }
     }
 
     /// remove a player from a level given a level ID and an account ID
     pub fn remove_from_level(&mut self, level_id: i32, account_id: i32) {
         let should_remove_level = self.levels.get_mut(&level_id).is_some_and(|level| {
-            level.remove(&account_id);
+            if let Some(index) = level.iter().position(|&x| x == account_id) {
+                level.remove(index);
+            }
+
             level.is_empty()
         });
 
diff --git a/server/game/src/server_thread/handlers/game.rs b/server/game/src/server_thread/handlers/game.rs
index 7a35a06c..da8e43a4 100644
--- a/server/game/src/server_thread/handlers/game.rs
+++ b/server/game/src/server_thread/handlers/game.rs
@@ -1,20 +1,15 @@
-use std::{
-    sync::{atomic::Ordering, Arc},
-    time::{SystemTime, UNIX_EPOCH},
-};
+use std::sync::{atomic::Ordering, Arc};
 
 use crate::{
     data::packets::PacketHeader,
     server_thread::{GameServerThread, PacketHandlingError, Result},
 };
 
-use globed_shared::logger::*;
-
 use super::*;
 use crate::data::*;
 
 /// max voice throughput in kb/s
-const MAX_VOICE_THROUGHPUT: usize = 8;
+pub const MAX_VOICE_THROUGHPUT: usize = 8;
 /// max voice packet size in bytes
 pub const MAX_VOICE_PACKET_SIZE: usize = 4096;
 
@@ -239,34 +234,6 @@ impl GameServerThread {
 
         let accid = self.account_id.load(Ordering::Relaxed);
 
-        // check the throughput
-        {
-            let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64;
-            let last_voice_packet = self.last_voice_packet.swap(now, Ordering::Relaxed);
-            let mut passed_time = now - last_voice_packet;
-
-            if passed_time == 0 {
-                passed_time = 1;
-            }
-
-            let total_size = packet.data.data.len();
-
-            // let total_size = packet
-            //     .data
-            //     .opus_frames
-            //     .iter()
-            //     .filter_map(|opt| opt.as_ref())
-            //     .map(Vec::len)
-            //     .sum::<usize>();
-
-            let throughput = total_size / passed_time as usize; // in kb per second
-
-            if throughput > MAX_VOICE_THROUGHPUT {
-                warn!("rejecting a voice packet, throughput above the limit: {}kb/s", throughput);
-                return Ok(());
-            }
-        }
-
         let vpkt = Arc::new(VoiceBroadcastPacket {
             player_id: accid,
             data: packet.data,
diff --git a/server/game/src/server_thread/handlers/mod.rs b/server/game/src/server_thread/handlers/mod.rs
index e78bd6dd..7ce01e34 100644
--- a/server/game/src/server_thread/handlers/mod.rs
+++ b/server/game/src/server_thread/handlers/mod.rs
@@ -1,7 +1,7 @@
 mod connection;
 mod game;
 
-pub use game::MAX_VOICE_PACKET_SIZE;
+pub use game::{MAX_VOICE_PACKET_SIZE, MAX_VOICE_THROUGHPUT};
 
 /// packet handler for a specific packet type
 macro_rules! gs_handler {
diff --git a/server/game/src/server_thread/mod.rs b/server/game/src/server_thread/mod.rs
index 9d2a5b41..ed291dd8 100644
--- a/server/game/src/server_thread/mod.rs
+++ b/server/game/src/server_thread/mod.rs
@@ -4,7 +4,7 @@ use std::{
         atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering},
         Arc, OnceLock,
     },
-    time::Duration,
+    time::{Duration, SystemTime, UNIX_EPOCH},
 };
 
 use parking_lot::Mutex as SyncMutex;
@@ -166,9 +166,9 @@ impl GameServerThread {
     /// fast packet sending with best-case zero heap allocation.
     /// if the packet size isn't known at compile time, calculate it and use `send_packet_fast_rough`
     async fn send_packet_fast<P: Packet + Encodable + KnownSize>(&self, packet: &P) -> Result<()> {
-        // in theory, the size is known at compile time, however for some reason,
-        // alloca manages to be significantly faster than a `[MaybeUninit<u8>; N]`.
-        // i have no idea why or how, but yeah.
+        // in theory, the size is known at compile time, so we could use a stack array here,
+        // instead of calling `send_packet_fast_rough` which uses alloca.
+        // however in practice, the performance difference is negligible, so we avoid code unnecessary code repetition.
         self.send_packet_fast_rough(packet, P::ENCODED_SIZE).await
     }
 
@@ -298,6 +298,44 @@ impl GameServerThread {
         Ok(buf)
     }
 
+    fn is_chat_packet_allowed(&self, voice: bool, len: usize) -> bool {
+        if !self.authenticated.load(Ordering::Relaxed) {
+            return false;
+        }
+
+        let accid = self.account_id.load(Ordering::Relaxed);
+        if self.game_server.chat_blocked(accid) {
+            return false;
+        }
+
+        if !voice {
+            return true;
+        }
+
+        if len > MAX_VOICE_PACKET_SIZE {
+            return false;
+        }
+
+        // check the throughput
+        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
+        let last_voice_packet = self.last_voice_packet.swap(now, Ordering::Relaxed);
+        let mut passed_time = now - last_voice_packet;
+
+        if passed_time == 0 {
+            passed_time = 1;
+        }
+
+        let throughput = len / passed_time as usize; // in kb per second
+
+        if throughput > MAX_VOICE_THROUGHPUT {
+            #[cfg(debug_assertions)]
+            warn!("rejecting a voice packet, throughput above the limit: {}kb/s", throughput);
+            return false;
+        }
+
+        true
+    }
+
     /// handle a message sent from the `GameServer`
     async fn handle_message(&self, message: ServerThreadMessage) -> Result<()> {
         match message {
@@ -329,20 +367,12 @@ impl GameServerThread {
         }
 
         // also for optimization, reject the voice packet immediately on certain conditions
-        if header.packet_id == VoicePacket::PACKET_ID || header.packet_id == ChatMessagePacket::PACKET_ID {
-            let accid = self.account_id.load(Ordering::Relaxed);
-            if self.game_server.chat_blocked(accid) {
-                debug!("blocking voice packet from {accid}");
-                return Ok(());
-            }
-
-            if header.packet_id == VoicePacket::PACKET_ID && message.len() > MAX_VOICE_PACKET_SIZE {
-                debug!(
-                    "blocking voice packet from {accid} because it's too big ({} bytes)",
-                    message.len()
-                );
-                return Ok(());
-            }
+        if (header.packet_id == VoicePacket::PACKET_ID || header.packet_id == ChatMessagePacket::PACKET_ID)
+            && !self.is_chat_packet_allowed(header.packet_id == VoicePacket::PACKET_ID, message.len())
+        {
+            #[cfg(debug_assertions)]
+            log::warn!("blocking text/voice packet from {}", self.account_id.load(Ordering::Relaxed));
+            return Ok(());
         }
 
         // reject cleartext credentials
diff --git a/server/game/tests/test.rs b/server/game/tests/test.rs
new file mode 100644
index 00000000..8b947787
--- /dev/null
+++ b/server/game/tests/test.rs
@@ -0,0 +1,138 @@
+// this doc is mostly for flamegraphs
+#![allow(clippy::wildcard_imports)]
+use bytebuffer::{ByteBuffer, ByteReader};
+use globed_game_server::{data::*, managers::PlayerManager};
+use std::hint::black_box;
+
+const ITERS: usize = 500_000;
+
+#[test]
+fn test_alloca_buffer() {
+    let data = PlayerAccountData {
+        account_id: 234_234_234,
+        name: FastString::from_str("hit his is my name"),
+        icons: PlayerIconData::default(),
+        special_user_data: Some(SpecialUserData {
+            name_color: Color3B { r: 10, g: 100, b: 200 },
+        }),
+    };
+
+    for _ in 0..ITERS {
+        let func1 = black_box(|| {
+            alloca::with_alloca(PlayerAccountData::ENCODED_SIZE * 64, |data_| {
+                let stackarray = unsafe {
+                    let ptr = data_.as_mut_ptr().cast::<u8>();
+                    std::slice::from_raw_parts_mut(ptr, std::mem::size_of_val(data_))
+                };
+
+                let mut buf = FastByteBuffer::new(stackarray);
+                for _ in 0..64 {
+                    buf.write_value(&data);
+                }
+
+                let mut buf = ByteReader::from_bytes(stackarray);
+                for _ in 0..64 {
+                    assert_eq!(buf.read_value::<PlayerAccountData>().unwrap().account_id, data.account_id);
+                }
+            });
+        });
+
+        func1();
+    }
+}
+
+#[test]
+fn test_fast_buffer() {
+    let data = PlayerAccountData {
+        account_id: 234_234_234,
+        name: FastString::from_str("hit his is my name"),
+        icons: PlayerIconData::default(),
+        special_user_data: Some(SpecialUserData {
+            name_color: Color3B { r: 10, g: 100, b: 200 },
+        }),
+    };
+
+    for _ in 0..ITERS {
+        let func1 = black_box(|| {
+            let mut stackarray = [0u8; PlayerAccountData::ENCODED_SIZE * 64];
+            let mut buf = FastByteBuffer::new(&mut stackarray);
+            for _ in 0..64 {
+                buf.write_value(&data);
+            }
+
+            let mut buf = ByteReader::from_bytes(&stackarray);
+            for _ in 0..64 {
+                assert_eq!(buf.read_value::<PlayerAccountData>().unwrap().account_id, data.account_id);
+            }
+        });
+
+        func1();
+    }
+}
+
+#[test]
+fn test_slow_buffer() {
+    let data = PlayerAccountData {
+        account_id: 234_234_234,
+        name: FastString::from_str("hit his is my name"),
+        icons: PlayerIconData::default(),
+        special_user_data: Some(SpecialUserData {
+            name_color: Color3B { r: 10, g: 100, b: 200 },
+        }),
+    };
+
+    for _ in 0..ITERS {
+        let func2 = black_box(|| {
+            let mut buffer = ByteBuffer::new();
+            for _ in 0..64 {
+                buffer.write_value(&data);
+            }
+
+            let mut reader = ByteReader::from_bytes(buffer.as_bytes());
+
+            for _ in 0..64 {
+                assert_eq!(reader.read_value::<PlayerAccountData>().unwrap().account_id, data.account_id);
+            }
+        });
+
+        func2();
+    }
+}
+
+#[test]
+fn test_player_manager() {
+    let mut manager = PlayerManager::new();
+
+    for level_id in 0..100 {
+        for account_id in 0..100 {
+            manager.add_to_level(level_id, level_id * 100 + account_id);
+            manager.set_player_data(level_id * 100 + account_id, &PlayerData {});
+        }
+    }
+
+    let mut total_players = 0;
+    for i in 0..100 {
+        let count = manager.get_player_count_on_level(i).unwrap_or(0);
+        assert_eq!(count, 100);
+
+        let total = manager.for_each_player_on_level(
+            i,
+            |_, _, p| {
+                *p += 1;
+                true
+            },
+            &mut total_players,
+        );
+
+        assert_eq!(total, count);
+    }
+
+    assert_eq!(total_players, 10000);
+
+    for level_id in 0..100 {
+        for account_id in 0..100 {
+            manager.remove_from_level(level_id, level_id * 100 + account_id);
+            manager.remove_player(level_id * 100 + account_id);
+        }
+    }
+}
diff --git a/server/shared/src/lib.rs b/server/shared/src/lib.rs
index 299c1376..b2d859e5 100644
--- a/server/shared/src/lib.rs
+++ b/server/shared/src/lib.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 
 // module reexports
 pub use colored;
@@ -24,7 +24,7 @@ pub struct SpecialUser {
 #[derive(Serialize, Deserialize)]
 pub struct GameServerBootData {
     pub protocol: u16,
-    pub no_chat: HashSet<i32>,
+    pub no_chat: Vec<i32>,
     pub special_users: HashMap<i32, SpecialUser>,
     pub tps: u32,
 }
diff --git a/src/audio/all.hpp b/src/audio/all.hpp
index 9976d6e6..18436420 100644
--- a/src/audio/all.hpp
+++ b/src/audio/all.hpp
@@ -5,4 +5,4 @@
 #include "audio_sample_queue.hpp"
 #include "audio_stream.hpp"
 #include "opus_codec.hpp"
-#include "voice_playback_manager.hpp"
\ No newline at end of file
+#include "voice_playback_manager.hpp"
diff --git a/src/audio/audio_frame.hpp b/src/audio/audio_frame.hpp
index 2baa30a9..0e06dc4a 100644
--- a/src/audio/audio_frame.hpp
+++ b/src/audio/audio_frame.hpp
@@ -44,4 +44,4 @@ class EncodedAudioFrame {
 };
 
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/audio_manager.cpp b/src/audio/audio_manager.cpp
index 37ec8ae4..7e73f858 100644
--- a/src/audio/audio_manager.cpp
+++ b/src/audio/audio_manager.cpp
@@ -36,7 +36,7 @@ std::vector<AudioRecordingDevice> GlobedAudioManager::getRecordingDevices() {
     FMOD_ERR_CHECK(
         this->getSystem()->getRecordNumDrivers(&numDrivers, &numConnected),
         "System::getRecordNumDrivers"
-    );
+    )
 
     for (int i = 0; i < numDrivers; i++) {
         out.push_back(this->getRecordingDevice(i));
@@ -52,7 +52,7 @@ std::vector<AudioPlaybackDevice> GlobedAudioManager::getPlaybackDevices() {
     FMOD_ERR_CHECK(
         this->getSystem()->getNumDrivers(&numDrivers),
         "System::getNumDrivers"
-    );
+    )
 
     for (int i = 0; i < numDrivers; i++) {
         out.push_back(this->getPlaybackDevice(i));
@@ -71,7 +71,7 @@ AudioRecordingDevice GlobedAudioManager::getRecordingDevice(int deviceId) {
         &device.speakerMode,
         &device.speakerModeChannels,
         &device.driverState
-    ), "System::getRecordDriverInfo");
+    ), "System::getRecordDriverInfo")
 
     device.id = deviceId;
     device.name = std::string(name);
@@ -88,7 +88,7 @@ AudioPlaybackDevice GlobedAudioManager::getPlaybackDevice(int deviceId) {
         &device.sampleRate,
         &device.speakerMode,
         &device.speakerModeChannels
-    ), "System::getDriverInfo");
+    ), "System::getDriverInfo")
 
     device.id = deviceId;
     device.name = std::string(name);
@@ -120,7 +120,7 @@ void GlobedAudioManager::validateDevices() {
 
 void GlobedAudioManager::startRecording(std::function<void(const EncodedAudioFrame&)> callback) {
     GLOBED_REQUIRE(this->recordDevice.id >= 0, "no recording device is set")
-    GLOBED_REQUIRE(!this->isRecording() && !recordActive, "attempting to record when already recording");
+    GLOBED_REQUIRE(!this->isRecording() && !recordActive, "attempting to record when already recording")
 
     FMOD_CREATESOUNDEXINFO exinfo = {};
 
@@ -141,12 +141,12 @@ void GlobedAudioManager::startRecording(std::function<void(const EncodedAudioFra
     FMOD_ERR_CHECK(
         this->getSystem()->createSound(nullptr, FMOD_2D | FMOD_OPENUSER | FMOD_LOOP_NORMAL, &exinfo, &recordSound),
         "System::createSound"
-    );
+    )
 
     FMOD_ERR_CHECK(
         this->getSystem()->recordStart(recordDevice.id, recordSound, true),
         "System::recordStart"
-    );
+    )
 
     recordQueuedStop = false;
     recordQueuedHalt = false;
@@ -159,7 +159,7 @@ void GlobedAudioManager::internalStopRecording() {
     FMOD_ERR_CHECK(
         this->getSystem()->recordStop(recordDevice.id),
         "System::recordStop"
-    );
+    )
 
     // if halting instead of stopping, don't call the callback
     if (recordQueuedHalt) {
@@ -171,7 +171,7 @@ void GlobedAudioManager::internalStopRecording() {
     }
 
     // cleanup
-    recordCallback = [](const auto& _){};
+    recordCallback = [](const auto&){};
     recordLastPosition = 0;
     recordChunkSize = 0;
     recordQueue.clear();
@@ -203,7 +203,7 @@ bool GlobedAudioManager::isRecording() {
     FMOD_ERR_CHECK(
         this->getSystem()->isRecording(this->recordDevice.id, &recording),
         "System::isRecording"
-    );
+    )
 
     return recording;
 }
@@ -213,7 +213,7 @@ FMOD::Channel* GlobedAudioManager::playSound(FMOD::Sound* sound) {
     FMOD_ERR_CHECK(
         this->getSystem()->playSound(sound, nullptr, false, &ch),
         "System::playSound"
-    );
+    )
 
     return ch;
 }
@@ -237,20 +237,20 @@ FMOD::Sound* GlobedAudioManager::createSound(const float* pcm, size_t samples, i
     FMOD_ERR_CHECK(this->getSystem()->createSound(
         nullptr, FMOD_2D | FMOD_OPENUSER | FMOD_CREATESAMPLE, &exinfo, &sound),
         "System::createSound"
-    );
+    )
 
     float* data;
     FMOD_ERR_CHECK(
         sound->lock(0, exinfo.length, (void**)&data, nullptr, nullptr, nullptr),
         "Sound::lock"
-    );
+    )
 
     std::memcpy(data, pcm, exinfo.length);
 
     FMOD_ERR_CHECK(
         sound->unlock(data, nullptr, exinfo.length, 0),
         "Sound::unlock"
-    );
+    )
 
     return sound;
 }
@@ -321,7 +321,7 @@ void GlobedAudioManager::audioThreadFunc() {
         FMOD_ERR_CHECK(
             this->getSystem()->getRecordPosition(recordDevice.id, &pos),
             "System::getRecordPosition"
-        );
+        )
 
         // if we are at the same position, do nothing
         if (pos == recordLastPosition) {
@@ -333,7 +333,7 @@ void GlobedAudioManager::audioThreadFunc() {
         FMOD_ERR_CHECK(
             recordSound->lock(0, recordChunkSize, (void**)&pcmData, nullptr, &pcmLen, nullptr),
             "Sound::lock"
-        );
+        )
 
         if (pos > recordLastPosition) {
             recordQueue.writeData(pcmData + recordLastPosition, pos - recordLastPosition);
@@ -349,7 +349,7 @@ void GlobedAudioManager::audioThreadFunc() {
         FMOD_ERR_CHECK(
             recordSound->unlock(pcmData, nullptr, pcmLen, 0),
             "Sound::unlock"
-        );
+        )
 
         if (recordQueue.size() >= VOICE_TARGET_FRAMESIZE) {
             float pcmbuf[VOICE_TARGET_FRAMESIZE];
@@ -482,4 +482,4 @@ std::string GlobedAudioManager::formatFmodError(FMOD_RESULT result, const char*
     return fmt::format("{} failed: [FMOD error {}] {}", whatFailed, (int)result, fmodErrorString(result));
 }
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/audio_manager.hpp b/src/audio/audio_manager.hpp
index 60e747dc..2f14e7a4 100644
--- a/src/audio/audio_manager.hpp
+++ b/src/audio/audio_manager.hpp
@@ -40,7 +40,7 @@ constexpr size_t VOICE_TARGET_FRAMESIZE = VOICE_TARGET_SAMPLERATE * VOICE_CHUNK_
 // This class is not thread safe. At all.
 class GlobedAudioManager {
 public:
-    GLOBED_SINGLETON(GlobedAudioManager);
+    GLOBED_SINGLETON(GlobedAudioManager)
     GlobedAudioManager();
     ~GlobedAudioManager();
 
@@ -127,4 +127,4 @@ class GlobedAudioManager {
     std::thread audioThreadHandle;
 };
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/audio_sample_queue.hpp b/src/audio/audio_sample_queue.hpp
index 098f2bb7..ce92d3c7 100644
--- a/src/audio/audio_sample_queue.hpp
+++ b/src/audio/audio_sample_queue.hpp
@@ -18,4 +18,4 @@ class AudioSampleQueue {
     std::vector<float> buf;
 };
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/audio_stream.cpp b/src/audio/audio_stream.cpp
index b330a169..61b18434 100644
--- a/src/audio/audio_stream.cpp
+++ b/src/audio/audio_stream.cpp
@@ -55,7 +55,7 @@ AudioStream::AudioStream() {
     auto system = vm.getSystem();
     res = system->createStream(nullptr, FMOD_OPENUSER | FMOD_2D | FMOD_LOOP_NORMAL, &exinfo, &sound);
 
-    GLOBED_REQUIRE(res == FMOD_OK, GlobedAudioManager::formatFmodError(res, "System::createStream"));
+    GLOBED_REQUIRE(res == FMOD_OK, GlobedAudioManager::formatFmodError(res, "System::createStream"))
 }
 
 AudioStream::~AudioStream() {
@@ -76,7 +76,6 @@ void AudioStream::start() {
 
 void AudioStream::writeData(const EncodedAudioFrame& frame) {
     auto& vm = GlobedAudioManager::get();
-    FMOD_RESULT res;
 
     const auto& frames = frame.getFrames();
     for (const auto& opusFrame : frames) {
@@ -87,4 +86,4 @@ void AudioStream::writeData(const EncodedAudioFrame& frame) {
     }
 }
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/audio_stream.hpp b/src/audio/audio_stream.hpp
index 9eba9dd0..79666abe 100644
--- a/src/audio/audio_stream.hpp
+++ b/src/audio/audio_stream.hpp
@@ -33,4 +33,4 @@ class AudioStream {
 };
 
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/opus_codec.cpp b/src/audio/opus_codec.cpp
index efe83308..39b0b6cb 100644
--- a/src/audio/opus_codec.cpp
+++ b/src/audio/opus_codec.cpp
@@ -80,7 +80,7 @@ void OpusCodec::freeData(EncodedOpusData data) {
 void OpusCodec::errcheck(const char* where) {
     if (_res != OPUS_OK) {
         const char* msg = opus_strerror(_res);
-        GLOBED_REQUIRE(false, std::string("opus error in ") + where + ": " + msg);
+        GLOBED_REQUIRE(false, std::string("opus error in ") + where + ": " + msg)
     }
 }
 
diff --git a/src/audio/opus_codec.hpp b/src/audio/opus_codec.hpp
index db342e65..e3e5ee41 100644
--- a/src/audio/opus_codec.hpp
+++ b/src/audio/opus_codec.hpp
@@ -72,4 +72,4 @@ class OpusCodec {
     void cleanup();
 };
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/voice_playback_manager.cpp b/src/audio/voice_playback_manager.cpp
index a3cb63b7..3f1044de 100644
--- a/src/audio/voice_playback_manager.cpp
+++ b/src/audio/voice_playback_manager.cpp
@@ -42,4 +42,4 @@ bool VoicePlaybackManager::isSpeaking(int playerId) {
     return !streams.at(playerId)->starving;
 }
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/audio/voice_playback_manager.hpp b/src/audio/voice_playback_manager.hpp
index 4a7ea8f8..f11f9aea 100644
--- a/src/audio/voice_playback_manager.hpp
+++ b/src/audio/voice_playback_manager.hpp
@@ -12,7 +12,7 @@
 */
 class VoicePlaybackManager {
 public:
-    GLOBED_SINGLETON(VoicePlaybackManager);
+    GLOBED_SINGLETON(VoicePlaybackManager)
     VoicePlaybackManager();
     ~VoicePlaybackManager();
 
@@ -27,4 +27,4 @@ class VoicePlaybackManager {
     std::unordered_map<int, std::unique_ptr<AudioStream>> streams;
 };
 
-#endif // GLOBED_VOICE_SUPPORT
\ No newline at end of file
+#endif // GLOBED_VOICE_SUPPORT
diff --git a/src/config.hpp b/src/config.hpp
index d918d5b9..c7e66b97 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -36,4 +36,4 @@
 
 
 
-#endif // GLOBED_IGNORE_CONFIG_HPP
\ No newline at end of file
+#endif // GLOBED_IGNORE_CONFIG_HPP
diff --git a/src/crypto/base_box.hpp b/src/crypto/base_box.hpp
index 8e8456de..71d95e14 100644
--- a/src/crypto/base_box.hpp
+++ b/src/crypto/base_box.hpp
@@ -64,4 +64,4 @@ class BaseCryptoBox {
     std::string decryptToString(const util::data::bytevector& src);
     // Decrypt `size` bytes from byte buffer `src` and return a string with the plaintext data.
     std::string decryptToString(const util::data::byte* src, size_t size);
-};
\ No newline at end of file
+};
diff --git a/src/crypto/box.cpp b/src/crypto/box.cpp
index f5d322d3..7666b15c 100644
--- a/src/crypto/box.cpp
+++ b/src/crypto/box.cpp
@@ -13,14 +13,14 @@ CryptoBox::CryptoBox(byte* key) {
         SHARED_KEY_LEN // sharedKey
     ));
 
-    CRYPTO_REQUIRE(memBasePtr != nullptr, "sodium_malloc returned nullptr");
+    CRYPTO_REQUIRE(memBasePtr != nullptr, "sodium_malloc returned nullptr")
 
     secretKey = memBasePtr; // base + 0
     publicKey = secretKey + SECRET_KEY_LEN; // base + 32
     peerPublicKey = publicKey + KEY_LEN; // base + 64
     sharedKey = peerPublicKey + KEY_LEN; // base + 96
 
-    CRYPTO_ERR_CHECK(func_box_keypair(publicKey, secretKey), "func_box_keypair failed");
+    CRYPTO_ERR_CHECK(func_box_keypair(publicKey, secretKey), "func_box_keypair failed")
 
     if (key != nullptr) {
         this->setPeerKey(key);
@@ -46,7 +46,7 @@ bytearray<CryptoBox::KEY_LEN> CryptoBox::extractPublicKey() noexcept {
 void CryptoBox::setPeerKey(byte* key) {
     std::memcpy(peerPublicKey, key, KEY_LEN);
 
-    CRYPTO_ERR_CHECK(func_box_beforenm(sharedKey, peerPublicKey, secretKey), "func_box_beforenm failed");
+    CRYPTO_ERR_CHECK(func_box_beforenm(sharedKey, peerPublicKey, secretKey), "func_box_beforenm failed")
 }
 
 constexpr size_t CryptoBox::nonceLength() {
@@ -62,7 +62,7 @@ size_t CryptoBox::encryptInto(const byte* src, byte* dest, size_t size) {
     util::crypto::secureRandom(nonce, NONCE_LEN);
 
     byte* ciphertext = dest + NONCE_LEN;
-    CRYPTO_ERR_CHECK(func_box_easy(ciphertext, src, size, nonce, sharedKey), "func_box_easy failed");
+    CRYPTO_ERR_CHECK(func_box_easy(ciphertext, src, size, nonce, sharedKey), "func_box_easy failed")
 
     // prepend the nonce
     std::memcpy(dest, nonce, NONCE_LEN);
@@ -71,7 +71,7 @@ size_t CryptoBox::encryptInto(const byte* src, byte* dest, size_t size) {
 }
 
 size_t CryptoBox::decryptInto(const util::data::byte* src, util::data::byte* dest, size_t size) {
-    CRYPTO_REQUIRE(size >= PREFIX_LEN, "message is too short");
+    CRYPTO_REQUIRE(size >= PREFIX_LEN, "message is too short")
 
     const byte* nonce = src;
     const byte* ciphertext = src + NONCE_LEN;
@@ -79,7 +79,7 @@ size_t CryptoBox::decryptInto(const util::data::byte* src, util::data::byte* des
     size_t plaintextLength = size - PREFIX_LEN;
     size_t ciphertextLength = size - NONCE_LEN;
 
-    CRYPTO_ERR_CHECK(func_box_open_easy(dest, ciphertext, ciphertextLength, nonce, sharedKey), "func_box_open_easy failed");
+    CRYPTO_ERR_CHECK(func_box_open_easy(dest, ciphertext, ciphertextLength, nonce, sharedKey), "func_box_open_easy failed")
 
     return plaintextLength;
 }
\ No newline at end of file
diff --git a/src/crypto/box.hpp b/src/crypto/box.hpp
index 74caafd2..185a8d62 100644
--- a/src/crypto/box.hpp
+++ b/src/crypto/box.hpp
@@ -3,7 +3,7 @@
 
 #include <sodium.h>
 
-class CryptoBox : public BaseCryptoBox {
+class CryptoBox final : public BaseCryptoBox {
 public:
 
 // XSalsa20 is theoretically slower and less secure, but still possible to use by defining GLOBED_USE_XSALSA20
@@ -47,11 +47,11 @@ class CryptoBox : public BaseCryptoBox {
     // This precomputes the shared key and stores it for use in all future operations.
     void setPeerKey(util::data::byte* src);
 
-    constexpr size_t nonceLength();
-    constexpr size_t macLength();
+    constexpr size_t nonceLength() override;
+    constexpr size_t macLength() override;
 
-    size_t encryptInto(const util::data::byte* src, util::data::byte* dest, size_t size);
-    size_t decryptInto(const util::data::byte* src, util::data::byte* dest, size_t size);
+    size_t encryptInto(const util::data::byte* src, util::data::byte* dest, size_t size) override;
+    size_t decryptInto(const util::data::byte* src, util::data::byte* dest, size_t size) override;
 
 private: // nuh uh
     util::data::byte* memBasePtr = nullptr;
@@ -61,4 +61,4 @@ class CryptoBox : public BaseCryptoBox {
     util::data::byte* peerPublicKey;
 
     util::data::byte* sharedKey;
-};
\ No newline at end of file
+};
diff --git a/src/crypto/secret_box.cpp b/src/crypto/secret_box.cpp
index d9536458..9f7e2347 100644
--- a/src/crypto/secret_box.cpp
+++ b/src/crypto/secret_box.cpp
@@ -7,13 +7,13 @@
 using namespace util::data;
 
 SecretBox::SecretBox(bytevector key) {
-    CRYPTO_REQUIRE(key.size() == crypto_secretbox_KEYBYTES, "provided key is too long or too short for SecretBox");
+    CRYPTO_REQUIRE(key.size() == crypto_secretbox_KEYBYTES, "provided key is too long or too short for SecretBox")
 
     this->key = reinterpret_cast<byte*>(sodium_malloc(
         crypto_secretbox_KEYBYTES
     ));
 
-    CRYPTO_REQUIRE(this->key != nullptr, "sodium_malloc returned nullptr");
+    CRYPTO_REQUIRE(this->key != nullptr, "sodium_malloc returned nullptr")
 
     std::memcpy(this->key, key.data(), crypto_secretbox_KEYBYTES);
 }
@@ -42,7 +42,7 @@ size_t SecretBox::encryptInto(const byte* src, byte* dest, size_t size) {
     util::crypto::secureRandom(nonce, NONCE_LEN);
 
     byte* ciphertext = dest + NONCE_LEN;
-    CRYPTO_ERR_CHECK(crypto_secretbox_easy(ciphertext, src, size, nonce, key), "crypto_secretbox_easy failed");
+    CRYPTO_ERR_CHECK(crypto_secretbox_easy(ciphertext, src, size, nonce, key), "crypto_secretbox_easy failed")
 
     // prepend the nonce
     std::memcpy(dest, nonce, NONCE_LEN);
@@ -51,7 +51,7 @@ size_t SecretBox::encryptInto(const byte* src, byte* dest, size_t size) {
 }
 
 size_t SecretBox::decryptInto(const byte* src, byte* dest, size_t size) {
-    CRYPTO_REQUIRE(size >= PREFIX_LEN, "message is too short");
+    CRYPTO_REQUIRE(size >= PREFIX_LEN, "message is too short")
 
     const byte* nonce = src;
     const byte* ciphertext = src + NONCE_LEN;
@@ -59,13 +59,13 @@ size_t SecretBox::decryptInto(const byte* src, byte* dest, size_t size) {
     size_t plaintextLength = size - PREFIX_LEN;
     size_t ciphertextLength = size - NONCE_LEN;
 
-    CRYPTO_ERR_CHECK(crypto_secretbox_open_easy(dest, ciphertext, ciphertextLength, nonce, key), "crypto_secretbox_open_easy failed");
+    CRYPTO_ERR_CHECK(crypto_secretbox_open_easy(dest, ciphertext, ciphertextLength, nonce, key), "crypto_secretbox_open_easy failed")
 
     return plaintextLength;
 }
 
 void SecretBox::setKey(const util::data::bytevector& src) {
-    GLOBED_REQUIRE(src.size() == crypto_secretbox_KEYBYTES, "key size is too small or too big for SecretBox");
+    GLOBED_REQUIRE(src.size() == crypto_secretbox_KEYBYTES, "key size is too small or too big for SecretBox")
     setKey(src.data());
 }
 
diff --git a/src/crypto/secret_box.hpp b/src/crypto/secret_box.hpp
index bf80b67b..49bafeab 100644
--- a/src/crypto/secret_box.hpp
+++ b/src/crypto/secret_box.hpp
@@ -8,7 +8,7 @@
 * uses a single secret key (or derives it from a passphrase) for data encryption.
 */
 
-class SecretBox : public BaseCryptoBox {
+class SecretBox final : public BaseCryptoBox {
 public:
     static const size_t NONCE_LEN = crypto_secretbox_NONCEBYTES;
     static const size_t MAC_LEN = crypto_secretbox_MACBYTES;
@@ -21,12 +21,12 @@ class SecretBox : public BaseCryptoBox {
 
     static SecretBox withPassword(const std::string& pw);
 
-    constexpr size_t nonceLength();
-    constexpr size_t macLength();
+    constexpr size_t nonceLength() override;
+    constexpr size_t macLength() override;
     using BaseCryptoBox::prefixLength;
 
-    size_t encryptInto(const util::data::byte* src, util::data::byte* dest, size_t size);
-    size_t decryptInto(const util::data::byte* src, util::data::byte* dest, size_t size);
+    size_t encryptInto(const util::data::byte* src, util::data::byte* dest, size_t size) override;
+    size_t decryptInto(const util::data::byte* src, util::data::byte* dest, size_t size) override;
 
     void setKey(const util::data::bytevector& src);
     void setKey(const util::data::byte* src);
@@ -35,4 +35,4 @@ class SecretBox : public BaseCryptoBox {
 
 private:
     util::data::byte* key = nullptr;
-};
\ No newline at end of file
+};
diff --git a/src/defs.hpp b/src/defs.hpp
index 10ca13a8..89f8441b 100644
--- a/src/defs.hpp
+++ b/src/defs.hpp
@@ -4,4 +4,4 @@
 #include <defs/assert.hpp>
 #include <defs/crash.hpp>
 #include <defs/platform.hpp>
-#include <defs/util.hpp>
\ No newline at end of file
+#include <defs/util.hpp>
diff --git a/src/defs/net.hpp b/src/defs/net.hpp
index 9dc8ea3d..5092f741 100644
--- a/src/defs/net.hpp
+++ b/src/defs/net.hpp
@@ -13,7 +13,7 @@
 
 /* windows includes */
 
-# include <ws2tcpip.h>
+# include <WS2tcpip.h>
 
 #elif defined(GLOBED_UNIX) // ^ windows | v unix
 
diff --git a/src/main.cpp b/src/main.cpp
index 6d4c9fe4..1ad89535 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -129,4 +129,4 @@ void printDebugInfo() {
     #if GLOBED_VOICE_SUPPORT
     geode::log::info("Opus version: {}", opus_get_version_string());
     #endif
-}
\ No newline at end of file
+}
diff --git a/src/managers/account_manager.cpp b/src/managers/account_manager.cpp
index 8379055c..af1d90be 100644
--- a/src/managers/account_manager.cpp
+++ b/src/managers/account_manager.cpp
@@ -25,7 +25,6 @@ void GlobedAccountManager::initialize(const std::string& name, int accountId, co
 void GlobedAccountManager::autoInitialize() {
     auto* gjam = GJAccountManager::get();
     auto& csm = CentralServerManager::get();
-    auto& gsm = GameServerManager::get();
 
     std::string activeCentralUrl = "";
 
diff --git a/src/managers/central_server_manager.cpp b/src/managers/central_server_manager.cpp
index 619c8c4c..b11ad93e 100644
--- a/src/managers/central_server_manager.cpp
+++ b/src/managers/central_server_manager.cpp
@@ -14,9 +14,7 @@ CentralServerManager::CentralServerManager() {
     if (empty) {
         this->addServer(CentralServer {
             .name = "Main server",
-            // .url = "https://globed.dankmeme.dev"
-            // TODO prod ^
-            .url = "http://127.0.0.1:41000"
+            .url = "https://globed.dankmeme.dev"
         });
     }
 
diff --git a/src/managers/central_server_manager.hpp b/src/managers/central_server_manager.hpp
index 93ff825d..6cc4149f 100644
--- a/src/managers/central_server_manager.hpp
+++ b/src/managers/central_server_manager.hpp
@@ -22,7 +22,7 @@ struct CentralServer {
 
 class CentralServerManager {
 public:
-    GLOBED_SINGLETON(CentralServerManager);
+    GLOBED_SINGLETON(CentralServerManager)
     CentralServerManager();
 
     // set the current active server, thread safe
diff --git a/src/managers/error_queues.hpp b/src/managers/error_queues.hpp
index b6a408f4..7ac6ddbc 100644
--- a/src/managers/error_queues.hpp
+++ b/src/managers/error_queues.hpp
@@ -11,7 +11,7 @@
 */
 
 class ErrorQueues {
-    GLOBED_SINGLETON(ErrorQueues);
+    GLOBED_SINGLETON(ErrorQueues)
     ErrorQueues();
 
     void warn(const std::string& message, bool print = true);
diff --git a/src/managers/game_server_manager.hpp b/src/managers/game_server_manager.hpp
index d97ee5ca..d8a72363 100644
--- a/src/managers/game_server_manager.hpp
+++ b/src/managers/game_server_manager.hpp
@@ -26,7 +26,7 @@ struct GameServer {
 // This class is fully thread safe to use.
 class GameServerManager {
 public:
-    GLOBED_SINGLETON(GameServerManager);
+    GLOBED_SINGLETON(GameServerManager)
     GameServerManager();
 
     constexpr static const char* STANDALONE_ID = "__standalone__server_id__";
diff --git a/src/managers/settings.hpp b/src/managers/settings.hpp
index ae62627a..c0a74353 100644
--- a/src/managers/settings.hpp
+++ b/src/managers/settings.hpp
@@ -6,7 +6,7 @@
 
 // Besides `getCached()`, this class is not thread safe (reason: Mod::getSavedValue/Mod::setSavedValue)
 class GlobedSettings {
-    GLOBED_SINGLETON(GlobedSettings);
+    GLOBED_SINGLETON(GlobedSettings)
     GlobedSettings();
 
     struct CachedSettings;
diff --git a/src/net/network_manager.cpp b/src/net/network_manager.cpp
index 8966a8cf..201adfee 100644
--- a/src/net/network_manager.cpp
+++ b/src/net/network_manager.cpp
@@ -33,7 +33,7 @@ NetworkManager::NetworkManager() {
         this->send(LoginPacket::create(gddata->accountId, gddata->accountName, authtoken));
     });
 
-    addBuiltinListener<KeepaliveResponsePacket>([this](auto packet) {
+    addBuiltinListener<KeepaliveResponsePacket>([](auto packet) {
         GameServerManager::get().finishKeepalive(packet->playerCount);
     });
 
@@ -54,7 +54,7 @@ NetworkManager::NetworkManager() {
         this->disconnect(true);
     });
 
-    addBuiltinListener<ServerNoticePacket>([this](auto packet) {
+    addBuiltinListener<ServerNoticePacket>([](auto packet) {
         ErrorQueues::get().notice(packet->message);
     });
 
diff --git a/src/net/udp_socket.cpp b/src/net/udp_socket.cpp
index cdf8eb14..c2a61123 100644
--- a/src/net/udp_socket.cpp
+++ b/src/net/udp_socket.cpp
@@ -26,7 +26,7 @@ bool UdpSocket::connect(const std::string& serverIp, unsigned short port) {
 }
 
 int UdpSocket::send(const char* data, unsigned int dataSize) {
-    GLOBED_REQUIRE(connected, "attempting to call UdpSocket::send on a disconnected socket");
+    GLOBED_REQUIRE(connected, "attempting to call UdpSocket::send on a disconnected socket")
 
     int retval = sendto(socket_, data, dataSize, 0, reinterpret_cast<struct sockaddr*>(&destAddr_), sizeof(destAddr_));
 
diff --git a/src/ui/error_check_node.cpp b/src/ui/error_check_node.cpp
index 516164d3..46a3aa9c 100644
--- a/src/ui/error_check_node.cpp
+++ b/src/ui/error_check_node.cpp
@@ -12,7 +12,7 @@ bool ErrorCheckNode::init() {
     return true;
 }
 
-void ErrorCheckNode::updateErrors(float _unused) {
+void ErrorCheckNode::updateErrors(float) {
     auto* currentScene = CCDirector::get()->getRunningScene();
     if (!currentScene || !currentScene->getChildren() || currentScene->getChildrenCount() == 0) return;
 
diff --git a/src/ui/error_check_node.hpp b/src/ui/error_check_node.hpp
index 292a0a63..91f6dddf 100644
--- a/src/ui/error_check_node.hpp
+++ b/src/ui/error_check_node.hpp
@@ -6,6 +6,6 @@ class ErrorCheckNode : public cocos2d::CCNode {
     static ErrorCheckNode* create();
 
 protected:
-    bool init();
-    void updateErrors(float _unused);
+    bool init() override;
+    void updateErrors(float dt);
 };
\ No newline at end of file
diff --git a/src/ui/hooks/menu_layer.hpp b/src/ui/hooks/menu_layer.hpp
index 2b109fb7..19ae4d72 100644
--- a/src/ui/hooks/menu_layer.hpp
+++ b/src/ui/hooks/menu_layer.hpp
@@ -50,7 +50,7 @@ class $modify(HookedMenuLayer, MenuLayer) {
         menu->updateLayout();
     }
 
-    void maybeUpdateButton(float _) {
+    void maybeUpdateButton(float) {
         bool authenticated = NetworkManager::get().established();
         if (authenticated != m_fields->btnActive) {
             m_fields->btnActive = authenticated;
diff --git a/src/ui/hooks/play_layer.hpp b/src/ui/hooks/play_layer.hpp
index 3d392134..f680697c 100644
--- a/src/ui/hooks/play_layer.hpp
+++ b/src/ui/hooks/play_layer.hpp
@@ -60,7 +60,6 @@ class $modify(GlobedPlayLayer, PlayLayer) {
         // is to only do it when the state actually changed. like we got a new best or
         // the attempt count increased by quite a bit.
 
-        auto scheduler = CCScheduler::get();
         this->rescheduleSender();
 
         this->setupCustomKeybinds();
@@ -94,14 +93,14 @@ class $modify(GlobedPlayLayer, PlayLayer) {
     void setupEventListeners() {
         auto& nm = NetworkManager::get();
 
-        nm.addListener<PlayerProfilesPacket>([this](PlayerProfilesPacket* packet) {
+        nm.addListener<PlayerProfilesPacket>([](PlayerProfilesPacket* packet) {
             auto& pcm = ProfileCacheManager::get();
             for (auto& player : packet->data) {
                 pcm.insert(player);
             }
         });
 
-        nm.addListener<LevelDataPacket>([this](LevelDataPacket* packet){
+        nm.addListener<LevelDataPacket>([](LevelDataPacket* packet){
             log::debug("Recv level data, {} players", packet->players.size());
         });
 
@@ -118,7 +117,7 @@ class $modify(GlobedPlayLayer, PlayLayer) {
             }
         });
 
-        nm.addListener<PlayerMetadataPacket>([this](PlayerMetadataPacket* packet) {
+        nm.addListener<PlayerMetadataPacket>([](PlayerMetadataPacket*) {
             // TODO handle player metadata
         });
     }
@@ -176,7 +175,7 @@ class $modify(GlobedPlayLayer, PlayLayer) {
     /* periodical selectors */
 
     // selSendPlayerData - runs 30 times per second
-    void selSendPlayerData(float _dt) {
+    void selSendPlayerData(float) {
         if (!this->established()) return;
         if (!this->isCurrentPlayLayer()) return;
         if (!this->accountForSpeedhack()) return;
@@ -218,7 +217,7 @@ class $modify(GlobedPlayLayer, PlayLayer) {
     bool accountForSpeedhack() {
         auto* sched = CCScheduler::get();
         auto ts = sched->getTimeScale();
-        if (ts != m_fields->lastKnownTimeScale) {
+        if (util::math::equal(ts, m_fields->lastKnownTimeScale)) {
             sched->unscheduleSelector(schedule_selector(GlobedPlayLayer::selSendPlayerData), this);
             this->rescheduleSender();
         }
diff --git a/src/ui/menu/main/globed_menu_layer.cpp b/src/ui/menu/main/globed_menu_layer.cpp
index 67839507..9f621e97 100644
--- a/src/ui/menu/main/globed_menu_layer.cpp
+++ b/src/ui/menu/main/globed_menu_layer.cpp
@@ -62,7 +62,7 @@ bool GlobedMenuLayer::init() {
 
     Build<CCSprite>::createSpriteName("miniSkull_001.png")
         .scale(1.2f)
-        .intoMenuItem([this](auto) {
+        .intoMenuItem([](auto) {
             // this->requestServerList();
             if (auto* popup = PlayerListPopup::create()) {
                 popup->m_noElasticity = true;
@@ -76,7 +76,7 @@ bool GlobedMenuLayer::init() {
 
     Build<CCSprite>::createSpriteName("d_skull01_001.png")
         .scale(1.2f)
-        .intoMenuItem([this](auto) {
+        .intoMenuItem([](auto) {
             GlobedAccountManager::get().clearAuthKey();
         })
         .id("btn-clear-authtoken"_spr)
@@ -86,7 +86,7 @@ bool GlobedMenuLayer::init() {
 
     Build<CCSprite>::createSpriteName("gjHand_05_001.png")
         .scale(1.2f)
-        .intoMenuItem([this](auto) {
+        .intoMenuItem([](auto) {
             if (auto* popup = ServerSwitcherPopup::create()) {
                 popup->m_noElasticity = true;
                 popup->show();
@@ -105,7 +105,7 @@ bool GlobedMenuLayer::init() {
     auto menu = CCMenu::create();
     this->addChild(menu);
 
-    util::ui::addBackButton(this, menu, util::ui::navigateBack);
+    util::ui::addBackButton(menu, util::ui::navigateBack);
 
     this->setKeyboardEnabled(true);
     this->setKeypadEnabled(true);
@@ -141,7 +141,7 @@ CCArray* GlobedMenuLayer::createServerList() {
 
     auto activeServer = gsm.active();
 
-    for (const auto [serverId, server] : gsm.getAllServers()) {
+    for (const auto& [serverId, server] : gsm.getAllServers()) {
         bool active = authenticated && serverId == activeServer;
         auto cell = ServerListCell::create(server, active);
         ret->addObject(cell);
@@ -150,9 +150,8 @@ CCArray* GlobedMenuLayer::createServerList() {
     return ret;
 }
 
-void GlobedMenuLayer::refreshServerList(float _) {
+void GlobedMenuLayer::refreshServerList(float) {
     auto& am = GlobedAccountManager::get();
-    auto& nm = NetworkManager::get();
     auto& csm = CentralServerManager::get();
 
     // if we do not have a session token from the central server, and are not in a standalone server, don't show game servers
@@ -273,7 +272,7 @@ void GlobedMenuLayer::keyBackClicked() {
     util::ui::navigateBack();
 }
 
-void GlobedMenuLayer::pingServers(float _) {
+void GlobedMenuLayer::pingServers(float) {
     NetworkManager::get().taskPingServers();
 }
 
diff --git a/src/ui/menu/main/signup_layer.cpp b/src/ui/menu/main/signup_layer.cpp
index c6c3f551..547fd8d5 100644
--- a/src/ui/menu/main/signup_layer.cpp
+++ b/src/ui/menu/main/signup_layer.cpp
@@ -22,7 +22,7 @@ bool GlobedSignupLayer::init() {
     this->setContentSize(listLayer->getScaledContentSize());
 
     Build<ButtonSprite>::create("Login", "goldFont.fnt", "GJ_button_01.png", 0.8f)
-        .intoMenuItem([this](auto) {
+        .intoMenuItem([](auto) {
             if (!GlobedSettings::get().getFlag("seen-signup-notice")) {
                 geode::createQuickPopup("Notice", CONSENT_MESSAGE, "Cancel", "Ok", [](auto, bool agreed){
                     if (agreed) {
diff --git a/src/ui/menu/main/signup_popup.cpp b/src/ui/menu/main/signup_popup.cpp
index d808d01d..72696969 100644
--- a/src/ui/menu/main/signup_popup.cpp
+++ b/src/ui/menu/main/signup_popup.cpp
@@ -80,7 +80,7 @@ void GlobedSignupPopup::onChallengeCreated(int levelId, const std::string& chtok
     }
 }
 
-void GlobedSignupPopup::commentUploadFinished(int _) {
+void GlobedSignupPopup::commentUploadFinished(int) {
     GameLevelManager::get()->m_commentUploadDelegate = nullptr;
 
     this->runAction(
@@ -96,7 +96,7 @@ void GlobedSignupPopup::onDelayedChallengeCompleted() {
     this->onChallengeCompleted(storedAuthcode);
 }
 
-void GlobedSignupPopup::commentUploadFailed(int cid, CommentError e) {
+void GlobedSignupPopup::commentUploadFailed(int, CommentError e) {
     GameLevelManager::get()->m_commentUploadDelegate = nullptr;
     this->onFailure(fmt::format("Comment upload failed: <cy>error {}</c>", (int)e));
 }
@@ -155,7 +155,7 @@ void GlobedSignupPopup::onFailure(const std::string& message) {
     this->onClose(this);
 }
 
-void GlobedSignupPopup::keyDown(cocos2d::enumKeyCodes key) {
+void GlobedSignupPopup::keyDown(cocos2d::enumKeyCodes) {
     // do nothing; the popup should be impossible to close manually
 }
 
diff --git a/src/ui/menu/server_switcher/add_server_popup.cpp b/src/ui/menu/server_switcher/add_server_popup.cpp
index aaabb0de..3c21defb 100644
--- a/src/ui/menu/server_switcher/add_server_popup.cpp
+++ b/src/ui/menu/server_switcher/add_server_popup.cpp
@@ -53,7 +53,7 @@ bool AddServerPopup::setup(int modifyingIndex, ServerSwitcherPopup* parent) {
 
     // create button
     Build<ButtonSprite>::create(modifyingIndex == -1 ? "Create" : "Save", "bigFont.fnt", "GJ_button_01.png", 0.8f)
-        .intoMenuItem([this, modifyingIndex](auto) {
+        .intoMenuItem([this](auto) {
             static std::regex serverPattern("^(https?://[^\\s]+[.][^\\s]+)$");
 
             std::string name = this->nameNode->getString();
diff --git a/src/util/crypto.cpp b/src/util/crypto.cpp
index 54de17e3..3ae56a62 100644
--- a/src/util/crypto.cpp
+++ b/src/util/crypto.cpp
@@ -43,7 +43,7 @@ bytevector pwHash(const byte* input, size_t len) {
         crypto_pwhash_OPSLIMIT_INTERACTIVE,
         crypto_pwhash_MEMLIMIT_INTERACTIVE,
         crypto_pwhash_ALG_DEFAULT
-    ), "crypto_pwhash failed");
+    ), "crypto_pwhash failed")
 
     return out;
 }
@@ -63,7 +63,7 @@ bytevector simpleHash(const byte* input, size_t size) {
         crypto_generichash_BYTES,
         input, size,
         nullptr, 0
-    ), "crypto_generichash failed");
+    ), "crypto_generichash failed")
 
     return out;
 }
@@ -78,7 +78,7 @@ std::string simpleTOTP(const bytevector& key) {
 }
 
 std::string simpleTOTPForPeriod(const byte *key, size_t keySize, uint64_t period) {
-    CRYPTO_REQUIRE(keySize == crypto_auth_hmacsha256_KEYBYTES, "invalid key size passed to simpleTOTPForPeriod");
+    CRYPTO_REQUIRE(keySize == crypto_auth_hmacsha256_KEYBYTES, "invalid key size passed to simpleTOTPForPeriod")
 
     if constexpr (GLOBED_LITTLE_ENDIAN) {
         period = byteswap(period);
@@ -179,7 +179,7 @@ bytevector base64Decode(const byte* source, size_t size, Base64Variant variant)
         out.data(), outMaxLen,
         reinterpret_cast<const char*>(source), size,
         nullptr, &outRealLen, nullptr, (int)variant
-    ), "invalid base64 string");
+    ), "invalid base64 string")
 
     out.resize(outRealLen); // necessary
 
@@ -224,7 +224,7 @@ bytevector hexDecode(const byte* source, size_t size) {
         out.data(), outLen,
         reinterpret_cast<const char*>(source), size,
         nullptr, &realOutLen, nullptr
-    ), "invalid hex string");
+    ), "invalid hex string")
 
     out.resize(realOutLen);
 
diff --git a/src/util/data.hpp b/src/util/data.hpp
index 38d12dee..cc32cf66 100644
--- a/src/util/data.hpp
+++ b/src/util/data.hpp
@@ -69,4 +69,4 @@ namespace util::data {
     constexpr size_t bitsToBytes(size_t bits) {
         return (bits + 7) / 8;
     }
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/util/debugging.cpp b/src/util/debugging.cpp
index c20828b3..2a6fd044 100644
--- a/src/util/debugging.cpp
+++ b/src/util/debugging.cpp
@@ -25,7 +25,7 @@ namespace util::debugging {
                 formatting::formatBytes(totalBytesOut),
                 formatting::formatBytes(totalBytesIn)
             );
-            geode::log::debug("Average bytes per packet: {}", formatting::formatBytes(bytesPerPacket));
+            geode::log::debug("Average bytes per packet: {}", formatting::formatBytes((uint64_t)bytesPerPacket));
 
             // sort packets by the counts
             std::vector<std::pair<packetid_t, size_t>> pc(packetCounts.begin(), packetCounts.end());
@@ -100,7 +100,7 @@ namespace util::debugging {
     [[noreturn]] void suicide(const std::source_location loc) {
         geode::log::error("suicide called at " + sourceLocation(loc) + ", terminating.");
 		geode::log::error("If you see this, something very, very bad happened.");
-        GLOBED_SUICIDE;
+        GLOBED_SUICIDE
     }
 #else
     std::string sourceLocation() {
@@ -110,11 +110,11 @@ namespace util::debugging {
     [[noreturn]] void suicide() {
         GLOBED_REQUIRE_LOG("suicide called at <unknown location>, terminating.");
         GLOBED_REQUIRE_LOG("If you see this, something very, very bad happened.");
-        GLOBED_SUICIDE;
+        GLOBED_SUICIDE
     }
 #endif
 
     void timedLog(const std::string& message) {
         geode::log::info("\r[{}] [Globed] {}", util::formatting::formatDateTime(util::time::now()), message);
     }
-}
\ No newline at end of file
+}
diff --git a/src/util/math.cpp b/src/util/math.cpp
index 626abb72..52d385e5 100644
--- a/src/util/math.cpp
+++ b/src/util/math.cpp
@@ -1,6 +1,14 @@
 #include "math.hpp"
 
 namespace util::math {
+    float snan(float val) {
+        return std::isnan(val) ? snan() : val;
+    }
+
+    float snan() {
+        return std::numeric_limits<float>::signaling_NaN();
+    }
+
     bool equal(float val1, float val2, float errorMargin) {
         return std::fabs(val2 - val1) < errorMargin;
     }
@@ -18,11 +26,11 @@ namespace util::math {
     }
 
     bool greaterOrEqual(float val1, float val2, float errorMargin) {
-        return val1 > val2 || equal(val1, val2);
+        return val1 > val2 || equal(val1, val2, errorMargin);
     }
 
     bool greaterOrEqual(double val1, double val2, double errorMargin) {
-        return val1 > val2 || equal(val1, val2);
+        return val1 > val2 || equal(val1, val2, errorMargin);
     }
 
     bool smaller(float val1, float val2, float errorMargin) {
@@ -34,10 +42,10 @@ namespace util::math {
     }
 
     bool smallerOrEqual(float val1, float val2, float errorMargin) {
-        return val1 < val2 || equal(val1, val2);
+        return val1 < val2 || equal(val1, val2, errorMargin);
     }
 
     bool smallerOrEqual(double val1, double val2, double errorMargin) {
-        return val1 < val2 || equal(val1, val2);
+        return val1 < val2 || equal(val1, val2, errorMargin);
     }
 }
\ No newline at end of file
diff --git a/src/util/math.hpp b/src/util/math.hpp
index 641b16a9..888f8ad8 100644
--- a/src/util/math.hpp
+++ b/src/util/math.hpp
@@ -5,6 +5,18 @@ namespace util::math {
     constexpr float FLOAT_ERROR_MARGIN = 0.002f;
     constexpr double DOUBLE_ERROR_MARGIN = 0.0001;
 
+    // if `val` is NaN, return a signaling NaN, otherwise return `val` unchanged
+    float snan(float val);
+    // returns a signaling NaN
+    float snan();
+
+    // Returns `true` if all passed numbers are valid. Returns `false` if at least one of them is NaN
+    template <typename... Args>
+    requires (std::floating_point<Args> && ...)
+    bool checkNotNaN(Args... args) {
+        return ((!std::isnan(args)) && ...);
+    }
+
     // `val1` == `val2`
     bool equal(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN);
     // `val1` == `val2`
diff --git a/src/util/net.cpp b/src/util/net.cpp
index 42ad1862..166c0c33 100644
--- a/src/util/net.cpp
+++ b/src/util/net.cpp
@@ -31,10 +31,10 @@ namespace util::net {
 
     std::string lastErrorString(int code) {
 #ifdef GLOBED_WIN32
-        char *s = NULL;
+        char *s = nullptr;
         if (FormatMessageA(
                 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
-                NULL, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&s, 0, NULL)
+                nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&s, 0, nullptr)
         == 0) {
             // some errors like WSA 10038 can raise ERROR_MR_MID_NOT_FOUND (0x13D)
             // which basically means the formatted message txt doesn't exist in the OS.
diff --git a/src/util/rng.cpp b/src/util/rng.cpp
index bef82fb6..4a47bd3c 100644
--- a/src/util/rng.cpp
+++ b/src/util/rng.cpp
@@ -64,4 +64,9 @@ namespace util::rng {
     template<> double Random::generate<double>() {
         return generate(0.0, 1.0);
     }
+
+    bool Random::genRatio(uint32_t numerator, uint32_t denominator) {
+        double probability = static_cast<double>(numerator) / denominator;
+        return this->generate<double>() < probability;
+    }
 }
\ No newline at end of file
diff --git a/src/util/rng.hpp b/src/util/rng.hpp
index b1d320f7..bd24a373 100644
--- a/src/util/rng.hpp
+++ b/src/util/rng.hpp
@@ -20,6 +20,9 @@ namespace util::rng {
         template <typename T>
         T generate(T min, T max);
 
+        // Has a `(numerator/denominator)` chance of returning true.
+        bool genRatio(uint32_t numerator, uint32_t denominator);
+
     private:
         std::mt19937_64 engine;
     };
diff --git a/src/util/sync.hpp b/src/util/sync.hpp
index 4f5b123c..deeae5e8 100644
--- a/src/util/sync.hpp
+++ b/src/util/sync.hpp
@@ -104,7 +104,7 @@ class WrappingMutex {
 
     class Guard {
     public:
-        Guard(std::shared_ptr<T> data, std::mutex& mutex) : mutex_(mutex), data_(data) {
+        Guard(std::shared_ptr<T> data, std::mutex& mutex) : data_(data), mutex_(mutex) {
             mutex_.lock();
         }
         ~Guard() {
diff --git a/src/util/time.cpp b/src/util/time.cpp
index a38f663b..15056f19 100644
--- a/src/util/time.cpp
+++ b/src/util/time.cpp
@@ -13,4 +13,4 @@ namespace util::time {
 
         return ss.str();
     }
-}
\ No newline at end of file
+}
diff --git a/src/util/time.hpp b/src/util/time.hpp
index f51fd1f6..2713c5c1 100644
--- a/src/util/time.hpp
+++ b/src/util/time.hpp
@@ -48,4 +48,4 @@ namespace util::time {
     }
 
     std::string nowPretty();
-}
\ No newline at end of file
+}
diff --git a/src/util/ui.cpp b/src/util/ui.cpp
index 66cfc855..3e4dc115 100644
--- a/src/util/ui.cpp
+++ b/src/util/ui.cpp
@@ -21,7 +21,7 @@ namespace util::ui {
             .parent(layer);
     }
 
-    void addBackButton(CCNode* parent, CCMenu* menu, std::function<void()> callback) {
+    void addBackButton(CCMenu* menu, std::function<void()> callback) {
         auto windowSize = CCDirector::get()->getWinSize();
         Build<CCSprite>::createSpriteName("GJ_arrow_01_001.png")
             .intoMenuItem([=](CCObject*) {
diff --git a/src/util/ui.hpp b/src/util/ui.hpp
index a694c309..994ceb76 100644
--- a/src/util/ui.hpp
+++ b/src/util/ui.hpp
@@ -5,6 +5,6 @@
 
 namespace util::ui {
     void addBackground(cocos2d::CCNode* layer);
-    void addBackButton(cocos2d::CCNode* parent, cocos2d::CCMenu* menu, std::function<void()> callback);
+    void addBackButton(cocos2d::CCMenu* menu, std::function<void()> callback);
     void navigateBack();
-}
\ No newline at end of file
+}