From 46c99050b94895edf76211e02a33b730a54777f9 Mon Sep 17 00:00:00 2001 From: protheeuz Date: Wed, 3 Jun 2026 17:22:59 +0700 Subject: [PATCH] docs: update rc2 benchmark report --- README.md | 89 +- benchmark/Benchmarks.md | 250 +-- benchmark/benchmark-result.json | 2742 +++++++++++++++------------- docs/api/channel.md | 14 +- docs/api/guard.md | 12 +- docs/api/packet.md | 6 +- docs/api/satset.md | 6 +- docs/api/types.md | 16 +- docs/guide/architecture.md | 78 +- docs/guide/channels.md | 14 +- docs/guide/development-patterns.md | 48 +- docs/guide/getting-started.md | 13 +- docs/guide/installation.md | 2 +- docs/guide/introduction.md | 6 +- docs/guide/packets.md | 15 +- docs/guide/security.md | 14 +- 16 files changed, 1760 insertions(+), 1565 deletions(-) diff --git a/README.md b/README.md index a331156..0240a75 100644 --- a/README.md +++ b/README.md @@ -11,71 +11,71 @@ -**sat·set** /sat-sèt/ *adjective (slang)*: Indonesian colloquialism for being rapid, efficient, and quick to act. +**sat·set** /sat-sèt/ *adjective (slang)*: Indonesian slang for doing something quickly. > *"Sat set, sampai."* means "Swiftly done." -Satset is a buffer-backed networking library for [Roblox](https://roblox.com). It handles packet serialization, batching, rate limiting, and state synchronization. The public API covers stateless events (Packets) and delta-compressed state (Channels). +Satset is a buffer-backed networking library for [Roblox](https://roblox.com). It handles packet serialization, batching, rate limiting, and state sync. The public API covers stateless events (Packets) and bitmask-tracked state (Channels). -Satset keeps the send path in Luau `buffer` objects where possible. Packet listeners receive decoded tables, and channel subscribers receive reconstructed state tables. +Satset keeps packet data in Luau `buffer` objects until the API boundary. Packet listeners receive decoded tables. Channel subscribers receive reconstructed state tables. # Performance Benchmarks -The [benchmark suite](benchmark/Benchmarks.md) compares Satset with native Roblox remotes and community networking libraries. It sends 200 events per frame for 10 seconds per payload. The tables report Roblox `Stats.DataSendKbps` p50 values normalized to a 60 FPS baseline. +The benchmark suite in `benchmark/` compares Satset with native Roblox remotes and community networking libraries. It sends 200 events per frame for 10 seconds per payload. The tables report Roblox `Stats.DataSendKbps` p50 values normalized to a 60 FPS baseline. -## Batching Profiles +## Latest Full Run -The latest benchmark uses two Satset profiles: +Lower normalized bandwidth is better. The latest full Studio run uses one Satset runtime path. -| Payload | Default normalized | Latency normalized | Default commits/frame | Latency commits/frame | -| :--- | ---: | ---: | ---: | ---: | -| Vectors | 118.13 | 38.84 | 3 | 1 | -| Booleans | 10.43 | 10.38 | 1 | 1 | -| Mixed | 4.29 | 4.31 | 1 | 1 | -| Entities | 117.53 | 39.15 | 3 | 1 | -| Strings | 899.63 | 96.37 | 8 | 1 | -| SingleValue | 2.39 | 2.38 | 1 | 1 | +| Payload | **Satset** rank | Winner | **Satset** Kbps (p50) | Notes | +| :--- | ---: | :--- | ---: | :--- | +| Vectors | 1 / 10 | Satset | 2.51 | 120.2K / 120.2K received | +| Booleans | 1 / 10 | Satset | 2.36 | 120.2K / 120.2K received | +| Mixed | 2 / 10 | Warp | 2.32 | Warp leads by about 3.6% | +| Entities | 1 / 10 | Satset | 2.64 | 120.2K / 120.2K received | +| SingleValue | 2 / 10 | Warp | 2.28 | Warp leads by about 3.8% | +| Strings | 1 / 10 valid rows | Satset | 3.28 | ByteNet stopped after 8 frames | -> [!NOTE] -> `Normalized` is `Stats.DataSendKbps` adjusted to 60 FPS. The default benchmark profile uses `reliableThreshold = 60000` and `maxPacketsPerFrame = 20`. The latency profile uses `reliableThreshold = 0` and `maxPacketsPerFrame = 0`. +Satset completed all payload rows at 601 frames, 60 FPS minimum, and zero packet loss. Reliable delta counters matched on both sides: `deltaEncoded == deltaDecoded == 600`. -Detailed methodology and raw data are in the [Benchmarks Report](benchmark/Benchmarks.md). +Raw data is in [benchmark-result.json](benchmark/benchmark-result.json). # Documentation -Technical documentation is available in the `docs/` directory: +Technical documentation lives in the `docs/` directory: - **[Architecture & Getting Started](docs/guide/getting-started.md)**: High-level overview and initialization. -- **[API Reference](docs/api/satset.md)**: Detailed breakdown of the `Satset` namespace. +- **[API Reference](docs/api/satset.md)**: `Satset` namespace, packets, channels, guards, and types. - **[Development Patterns](docs/guide/development-patterns.md)**: Design rules and performance constraints. - **[Security & Guard](docs/guide/security.md)**: Documentation on the token bucket rate limiting implementation. - **[Serialization Types](docs/api/types.md)**: Available data types for buffer-backed schemas. # Contributing -Contributions are welcome! Please review our **[Contribution Guide](CONTRIBUTING.md)** and **[Development Patterns](docs/guide/development-patterns.md)** before submitting a pull request. +Before opening a pull request, read the **[Contribution Guide](CONTRIBUTING.md)** and **[Development Patterns](docs/guide/development-patterns.md)**. # Features ## Hybrid Networking Engine -Satset provides two distinct communication modes: +Satset has two public networking surfaces: -- **Packets (Stateless)**: For one-off events like character actions or effects. These are batched automatically every frame to minimize RemoteEvent overhead. -- **Channels (Stateful)**: The core state synchronization engine. It tracks changes to a defined schema and transmits only the dirty fields (deltas) using bitmask-based compression. +- **Packets (Stateless)**: One-off events like character actions or effects. Satset batches them every frame. +- **Channels (Stateful)**: State sync for fixed-size schemas. Channels write state into a buffer, mark dirty fields with a bitmask, and send changed bytes between keyframes. ## Implementation Details - **Buffer-backed batching**: Outgoing payloads are encoded into Luau buffers and committed as exact-size buffers before transport. -- **Nested Structs**: Use `Satset.struct(schema)` to create reusable, composable type objects for complex nested schemas. -- **Readable Type Names**: Provides human-readable aliases (`string`, `uint8`, `float64`, etc.) alongside shorthand forms for cleaner, more self-documenting schemas. +- **Reliable run grouping**: Same-packet reliable runs share one packet id and one run count. +- **Reliable delta encoding**: Direct reliable traffic tracks the previous batch and XORs same-size batches. Broadcast reliable traffic stays raw. +- **Nested structs**: Use `Satset.struct(schema)` to build reusable type objects. +- **ByteNet-style aliases**: `string`, `uint8`, `float64`, and related names map to Satset's shorthand types. - **Packet dispatch**: Incoming batches are decoded in place. Each packet listener receives a decoded table. -- **Built-in Security**: Relies on Luau's native buffer bounds checks. Payload errors are caught via `pcall` before they reach game code. -- **Buffer Safety**: Dynamic data (strings/arrays) is capped relative to the physical buffer size to prevent memory-related issues. -- **Sanitized Floats**: Floating-point types (`f32`, `f64`, `Vector3`, etc.) are clamped to 0 if they are `NaN` or `±Infinity` to prevent state corruption. -- **Header Stripping**: Automatically identifies fixed-size schemas and omits size headers when possible to reduce protocol overhead. -- **Batch Segmentation**: Reliable batches split at `reliableThreshold`. Unreliable batches split around the configured unreliable threshold, which defaults to 900 bytes. -- **Guard**: Built-in server-side rate limiting using a token bucket algorithm to prevent spam. +- **Bounds checks**: Payload errors are caught through protected calls before game code receives data. +- **Buffer safety**: Dynamic data, such as strings and arrays, is capped against the physical buffer size. +- **Float sanitization**: Floating-point types (`f32`, `f64`, `Vector3`, etc.) clamp `NaN` and `±Infinity` to `0`. +- **Header stripping**: Fixed-size schemas omit per-payload size headers. +- **Guard**: Built-in server-side rate limiting through a token bucket. # Architecture @@ -120,12 +120,13 @@ flowchart TB SR --> SC SR --> SN SC --> TP + TP --> SN PK -->|"allocate stream slot"| BT PK -->|"encodeInto(buffer, offset)"| SR CH -->|"encodeDelta(bitmask)"| BT - BT -->|"flush & segment"| BR + BT -->|"flush, group, delta"| BR BR --> RE BR --> URE @@ -136,7 +137,8 @@ flowchart TB GD -->|"consume(player)"| PK GD -->|"consume(player)"| CH - SN -.->|"clamp NaN/Inf"| SR + SN -.->|"bounds + float checks"| SR + SN -.->|"float checks"| TP ``` For a detailed step-by-step walkthrough of a packet's lifecycle, see the [Architecture Guide](docs/guide/architecture.md). @@ -164,12 +166,14 @@ local Satset = require(ReplicatedStorage.Packages.Satset) Satset.start({ guard = { - maxTokens = 60, - refillRate = 30, + maxTokens = 1000, + refillRate = 500, + studioBypass = true, }, batching = { - reliableThreshold = 60000, -- Split reliable batches above 60 KB - maxPacketsPerFrame = 0, -- Send all ready batches each frame + reliableThreshold = 0, -- Commit reliable traffic at the frame flush + unreliableThreshold = 900, -- Keep UnreliableRemoteEvent payloads small + maxPacketsPerFrame = 0, -- No per-frame send cap } }) ``` @@ -227,9 +231,9 @@ Packets.Damage:listen(function(data) end) ``` -## Channels (Stateful Synchronization) +## Channels (Stateful Sync) -Channels are for data that has "state" (like health or positions). They use **delta-compression** and are much more efficient than packets for frequent updates. +Channels are for data that has state, such as health or positions. The first flush sends a full keyframe. Later flushes send only fields marked by the dirty bitmask. **Shared Definition:** @@ -247,7 +251,7 @@ return { position = Types.Vector3Quantized(2048) }, unreliable = true, - resyncInterval = 5 -- Periodic keyframe to prevent drift + resyncInterval = 5 -- Periodic keyframe after dropped unreliable updates }) } ``` @@ -257,13 +261,13 @@ return { ```luau local Channels = require(path.to.Shared.Channels) --- Create an entity instance for a player +-- Create state for a player local entity = Channels.PlayerState:create(player.UserId, { health = 100, position = Vector3.new(0, 5, 0) }) --- Update state (only changed fields are transmitted) +-- Only changed fields are sent on the next flush entity:set("health", 85) ``` @@ -272,7 +276,6 @@ entity:set("health", 85) ```luau local Channels = require(path.to.Shared.Channels) --- Subscribe to state changes Channels.PlayerState:subscribe(function(entityId, state) print("Entity", entityId, "updated. Health:", state.health) end) diff --git a/benchmark/Benchmarks.md b/benchmark/Benchmarks.md index 68c69d6..0c23beb 100644 --- a/benchmark/Benchmarks.md +++ b/benchmark/Benchmarks.md @@ -1,153 +1,175 @@ -# [Satset](https://github.com/bookek/satset) Networking Benchmarks +# [**Satset**](https://github.com/bookek/satset) Networking Benchmarks **Last updated:** June 3, 2026 -These benchmarks compare **Satset** with native Roblox remotes and several community networking libraries. The runner fires 200 events per frame for 10 seconds with one local player in Roblox Studio. A `120K / 120K` sent/received count means 120,000 events were sent by the client and verified by the server. +These benchmarks compare **Satset** with native Roblox remotes and community networking libraries. The runner fires 200 events per frame for 10 seconds with one local player in Roblox Studio. A `120K / 120K` sent/received count means 120,000 events were sent by the client and verified by the server. The tables use the raw fields from `benchmark-result.json`. -- `Normalized` is Roblox `Stats.DataSendKbps` adjusted to a 60 FPS baseline. -- `Drain` is seconds spent waiting for bandwidth to return near baseline after each row. +- `Kbps (p50)` is the p50 value from Roblox `Stats.DataSendKbps`, adjusted to a 60 FPS baseline. The runner does not convert it to `KBps`. +- `Drain (s)` is seconds spent waiting for bandwidth to return near baseline after each row. - `Loss` is only `Sent` vs `Received`. A partial run can still show `0.0%` loss. -- Treat rows with low frame counts or partial packet counts as incomplete rows, not winners. +- Treat rows with low frame counts, low min FPS, or partial packet counts as incomplete rows, not wins. --- ## Result Summary -This run uses one Satset benchmark control: +This run uses one **Satset** runtime path: ```luau reliableThreshold = 0 +unreliableThreshold = 900 maxPacketsPerFrame = 0 ``` -That is not a public Satset mode. It is the current benchmark control for the rc.2 candidate behavior: one reliable commit per frame. +**Satset** completed every payload row at `601` frames, `60` min FPS, and `120.2K / 120.2K` sent/received. Direct reliable traffic also matched its delta counters on both sides: `deltaEncoded = 600` and `deltaDecoded = 600`. -Satset now sends one reliable commit per frame in every Satset row. The data is mostly stable, but not perfectly full-volume: +[QuickNet][quicknet] is now part of the benchmark. Its adapter raises the rate limit with `SetRateLimit(1_000_000, 1)` so the run measures its payload path instead of its default anti-spam limit. -| Payload | Frames | Min FPS | Sent / Received | Commits / Frames | Normalized | Drain | -| :--- | ---: | ---: | :--- | :--- | ---: | ---: | -| Vectors | 600 | 59 | 120K / 120K | 600 / 600 | 39.02 | 7.82 | -| Booleans | 601 | 60 | 120.2K / 120.2K | 601 / 601 | 10.34 | 5.22 | -| Mixed | 600 | 60 | 120K / 120K | 600 / 600 | 4.34 | 4.23 | -| Entities | 599 | 59 | 119.8K / 119.8K | 599 / 599 | 38.96 | 7.77 | -| SingleValue | 601 | 60 | 120.2K / 120.2K | 601 / 601 | 2.46 | 3.43 | -| Strings | 601 | 60 | 120.2K / 120.2K | 601 / 601 | 96.43 | 9.75 | +| Payload | Winner | **Satset** rank | **Satset** Kbps (p50) | [QuickNet][quicknet] Kbps (p50) | Note | +| :--- | :--- | ---: | ---: | ---: | :--- | +| Vectors | **Satset** | 1 / 10 | 2.51 | 38.75 | **Satset** leads the full-volume rows. | +| Booleans | **Satset** | 1 / 10 | 2.36 | 10.15 | [QuickNet][quicknet] is partial at `571` frames. | +| Mixed | [Warp][warp] | 2 / 10 | 2.32 | 4.17 | [Warp][warp] leads **Satset** by about 3.6%. | +| Entities | **Satset** | 1 / 10 | 2.64 | 38.95 | [QuickNet][quicknet] completed 601 frames. | +| SingleValue | [Warp][warp] | 2 / 10 | 2.28 | 2.43 | [Warp][warp] leads **Satset** by about 3.8%. | +| Strings | **Satset** | 1 / 10 valid rows | 3.28 | 96.45 | [ByteNet][bytenet] went down over-capacity after 8 frames. | -QuickNet was added to this run. Its rate limit is raised in the adapter so the benchmark measures the payload path instead of QuickNet's default anti-spam behavior. +> **Note:** The report keeps Roblox's `Stats.DataSendKbps` naming and writes the unit as `Kbps`. No byte-per-second conversion is applied. -Main read: +Conclusion: **Satset** wins 4 of 6 payloads by valid normalized p50. [Warp][warp] still leads Mixed and SingleValue. [QuickNet][quicknet] is now a serious comparison target and completes 5 of 6 rows at full volume. -- Satset no longer has the old fragmented default-path bandwidth gap. -- QuickNet is close to Satset in Vectors, Booleans, Strings, and SingleValue. -- QuickNet is lower than Satset in Mixed in this run. -- QuickNet's Entities row is partial at `110.4K / 110.4K`, so that row should not be treated as a clean win. -- Warp still has the lowest raw bandwidth in several payloads, but some Warp rows are partial too. +## **Satset** Counters + +| Payload | Reliable commits / Frames | Delta encoded / decoded | Delta zero ratio | +| :--- | :--- | :--- | ---: | +| Vectors | 601 / 601 | 600 / 600 | 99.83% | +| Booleans | 601 / 601 | 600 / 600 | 99.83% | +| Mixed | 601 / 601 | 600 / 600 | 99.80% | +| Entities | 601 / 601 | 600 / 600 | 99.83% | +| SingleValue | 601 / 601 | 600 / 600 | 98.86% | +| Strings | 601 / 601 | 600 / 600 | 99.83% | + +> **Note:** The delta zero ratio is `deltaZeroBytes / deltaInputBytes`. + +Conclusion: the reliable XOR pass produced mostly zero bytes on this benchmark shape, and the sender/receiver delta counters matched. --- ## Vectors -| Library | Frames | Min FPS | Normalized | Drain | Sent / Received | Loss | +| Library | Frames | Min FPS | Kbps (p50) | Drain (s) | Sent / Received | Loss | | :--- | ---: | ---: | ---: | ---: | :--- | ---: | -| Satset | 600 | 59 | 39.02 | 7.82 | 120K / 120K | 0.0% | -| QuickNet | 600 | 60 | 38.97 | 8.80 | 120K / 120K | 0.0% | -| Warp | 564 | 54 | 2.81 | 4.94 | 112.8K / 112.8K | 0.0% | -| ByteNet | 600 | 60 | 72.51 | 9.32 | 120K / 120K | 0.0% | -| NetRay | 600 | 60 | 72.58 | 9.33 | 120K / 120K | 0.0% | -| Zap | 597 | 57 | 72.83 | 9.37 | 119.4K / 119.4K | 0.0% | -| Blink | 600 | 60 | 72.63 | 9.35 | 120K / 120K | 0.0% | -| Packet | 601 | 60 | 72.64 | 9.27 | 120.2K / 120.2K | 0.0% | -| BridgeNet2 | 576 | 56 | 15,633.83 | 22.15 | 115.2K / 115.2K | 0.0% | -| Roblox | 600 | 60 | 84,985.70 | 9.92 | 120K / 120K | 0.0% | - -Satset and QuickNet are effectively tied on bandwidth here. Warp is much lower, but its row is partial. +| **Satset** | 601 | 60 | 2.51 | 3.72 | 120.2K / 120.2K | 0.0% | +| [QuickNet][quicknet] | 601 | 60 | 38.75 | 8.28 | 120.2K / 120.2K | 0.0% | +| [Warp][warp] | 600 | 60 | 4.03 | 4.41 | 120K / 120K | 0.0% | +| [ByteNet][bytenet] | 600 | 60 | 72.62 | 9.78 | 120K / 120K | 0.0% | +| [NetrayCompile][netray-compile] | 600 | 60 | 72.49 | 9.83 | 120K / 120K | 0.0% | +| [Zap][zap] | 570 | 57 | 72.13 | 10.68 | 114K / 114K | 0.0% | +| [Blink][blink] | 600 | 60 | 72.71 | 10.35 | 120K / 120K | 0.0% | +| [Packet][packet] | 600 | 60 | 72.41 | 9.83 | 120K / 120K | 0.0% | +| [BridgeNet2][bridgenet2] | 455 | 55 | 14,744.88 | 23.75 | 91K / 91K | 0.0% | +| Roblox | 600 | 60 | 80,782.21 | 10.38 | 120K / 120K | 0.0% | + +> **Note:** [Zap][zap] and [BridgeNet2][bridgenet2] are partial rows here. + +Conclusion: **Satset** is the lowest valid row. [Warp][warp] is second among full-volume rows. ## Booleans -| Library | Frames | Min FPS | Normalized | Drain | Sent / Received | Loss | +| Library | Frames | Min FPS | Kbps (p50) | Drain (s) | Sent / Received | Loss | | :--- | ---: | ---: | ---: | ---: | :--- | ---: | -| Satset | 601 | 60 | 10.34 | 5.22 | 120.2K / 120.2K | 0.0% | -| QuickNet | 600 | 60 | 10.25 | 5.35 | 120K / 120K | 0.0% | -| Warp | 600 | 60 | 2.52 | 3.73 | 120K / 120K | 0.0% | -| ByteNet | 600 | 60 | 18.98 | 6.27 | 120K / 120K | 0.0% | -| NetRay | 600 | 60 | 10.33 | 5.27 | 120K / 120K | 0.0% | -| Zap | 591 | 58 | 29.25 | 7.25 | 118.2K / 118.2K | 0.0% | -| Blink | 600 | 60 | 29.36 | 7.30 | 120K / 120K | 0.0% | -| Packet | 600 | 60 | 23.63 | 6.73 | 120K / 120K | 0.0% | -| BridgeNet2 | 581 | 57 | 23,921.60 | 23.14 | 116.2K / 116.2K | 0.0% | -| Roblox | 533 | 53 | 59,674.46 | 10.34 | 106.6K / 106.6K | 0.0% | - -QuickNet is slightly lower than Satset. Satset is in the same range as NetRay. +| **Satset** | 601 | 60 | 2.36 | 3.70 | 120.2K / 120.2K | 0.0% | +| [QuickNet][quicknet] | 571 | 58 | 10.15 | 5.72 | 114.2K / 114.2K | 0.0% | +| [Warp][warp] | 600 | 60 | 2.95 | 4.32 | 120K / 120K | 0.0% | +| [ByteNet][bytenet] | 600 | 60 | 19.03 | 6.77 | 120K / 120K | 0.0% | +| [NetrayCompile][netray-compile] | 600 | 60 | 10.28 | 5.75 | 120K / 120K | 0.0% | +| [Zap][zap] | 600 | 60 | 29.39 | 7.84 | 120K / 120K | 0.0% | +| [Blink][blink] | 537 | 57 | 28.87 | 8.02 | 107.4K / 107.4K | 0.0% | +| [Packet][packet] | 559 | 54 | 29.30 | 7.70 | 111.8K / 111.8K | 0.0% | +| [BridgeNet2][bridgenet2] | 582 | 53 | 24,010.09 | 26.70 | 116.4K / 116.4K | 0.0% | +| Roblox | 559 | 53 | 50,833.59 | 11.61 | 111.8K / 111.8K | 0.0% | + +> **Note:** [QuickNet][quicknet], [Blink][blink], [Packet][packet], [BridgeNet2][bridgenet2], and Roblox are partial rows. + +Conclusion: **Satset** wins the valid rows. [Warp][warp] is the closest full-volume competitor. ## Mixed -| Library | Frames | Min FPS | Normalized | Drain | Sent / Received | Loss | +| Library | Frames | Min FPS | Kbps (p50) | Drain (s) | Sent / Received | Loss | | :--- | ---: | ---: | ---: | ---: | :--- | ---: | -| Satset | 600 | 60 | 4.34 | 4.23 | 120K / 120K | 0.0% | -| QuickNet | 600 | 60 | 4.18 | 4.08 | 120K / 120K | 0.0% | -| Warp | 600 | 60 | 2.40 | 3.52 | 120K / 120K | 0.0% | -| ByteNet | 598 | 58 | 4.97 | 4.25 | 119.6K / 119.6K | 0.0% | -| NetRay | 515 | 53 | 4.92 | 4.23 | 103K / 103K | 0.0% | -| Zap | 600 | 60 | 4.94 | 4.22 | 120K / 120K | 0.0% | -| Blink | 597 | 58 | 5.02 | 4.22 | 119.4K / 119.4K | 0.0% | -| Packet | 601 | 60 | 4.84 | 4.27 | 120.2K / 120.2K | 0.0% | -| BridgeNet2 | 600 | 60 | 1,680.62 | 16.40 | 120K / 120K | 0.0% | -| Roblox | 600 | 60 | 3,019.90 | 9.33 | 120K / 120K | 0.0% | - -QuickNet is the lowest full-volume row among the typed libraries here. Warp is still lower overall. +| **Satset** | 601 | 60 | 2.32 | 3.69 | 120.2K / 120.2K | 0.0% | +| [QuickNet][quicknet] | 600 | 60 | 4.17 | 4.20 | 120K / 120K | 0.0% | +| [Warp][warp] | 600 | 60 | 2.24 | 3.70 | 120K / 120K | 0.0% | +| [ByteNet][bytenet] | 600 | 60 | 5.01 | 4.45 | 120K / 120K | 0.0% | +| [NetrayCompile][netray-compile] | 600 | 60 | 4.98 | 4.80 | 120K / 120K | 0.0% | +| [Zap][zap] | 600 | 60 | 4.97 | 4.74 | 120K / 120K | 0.0% | +| [Blink][blink] | 600 | 60 | 5.10 | 4.55 | 120K / 120K | 0.0% | +| [Packet][packet] | 599 | 60 | 4.87 | 4.43 | 119.8K / 119.8K | 0.0% | +| [BridgeNet2][bridgenet2] | 388 | 50 | 1,252.08 | 20.53 | 77.6K / 77.6K | 0.0% | +| Roblox | 500 | 50 | 2,976.21 | 9.70 | 100K / 100K | 0.0% | + +> **Note:** [BridgeNet2][bridgenet2] and Roblox are partial rows. [Packet][packet] is near full volume at 599 frames. + +Conclusion: [Warp][warp] is the lowest row. **Satset** is second and about 3.6% higher than [Warp][warp]. ## Entities -| Library | Frames | Min FPS | Normalized | Drain | Sent / Received | Loss | +| Library | Frames | Min FPS | Kbps (p50) | Drain (s) | Sent / Received | Loss | | :--- | ---: | ---: | ---: | ---: | :--- | ---: | -| Satset | 599 | 59 | 38.96 | 7.77 | 119.8K / 119.8K | 0.0% | -| QuickNet | 552 | 54 | 38.56 | 7.77 | 110.4K / 110.4K | 0.0% | -| Warp | 570 | 48 | 2.64 | 4.23 | 114K / 114K | 0.0% | -| ByteNet | 600 | 60 | 38.89 | 8.05 | 120K / 120K | 0.0% | -| NetRay | 600 | 60 | 38.94 | 7.87 | 120K / 120K | 0.0% | -| Zap | 600 | 60 | 38.91 | 7.78 | 120K / 120K | 0.0% | -| Blink | 594 | 58 | 38.99 | 7.87 | 118.8K / 118.8K | 0.0% | -| Packet | 531 | 48 | 38.81 | 7.74 | 106.2K / 106.2K | 0.0% | -| BridgeNet2 | 187 | 18 | 100,015.96 | 26.13 | 37.4K / 37.4K | 0.0% | -| Roblox | 179 | 17 | 487,420.60 | 13.75 | 35.8K / 35.8K | 0.0% | - -ByteNet, NetRay, and Zap have the cleanest full-volume rows here. Satset is near full-volume. QuickNet is partial. +| **Satset** | 601 | 60 | 2.64 | 3.77 | 120.2K / 120.2K | 0.0% | +| [QuickNet][quicknet] | 601 | 60 | 38.95 | 8.32 | 120.2K / 120.2K | 0.0% | +| [Warp][warp] | 584 | 56 | 3.06 | 5.13 | 116.8K / 116.8K | 0.0% | +| [ByteNet][bytenet] | 600 | 60 | 38.84 | 9.33 | 120K / 120K | 0.0% | +| [NetrayCompile][netray-compile] | 600 | 60 | 39.01 | 8.04 | 120K / 120K | 0.0% | +| [Zap][zap] | 600 | 60 | 39.05 | 8.04 | 120K / 120K | 0.0% | +| [Blink][blink] | 600 | 60 | 38.95 | 8.40 | 120K / 120K | 0.0% | +| [Packet][packet] | 564 | 56 | 38.63 | 8.37 | 112.8K / 112.8K | 0.0% | +| [BridgeNet2][bridgenet2] | 153 | 16 | 103,551.54 | 26.68 | 30.6K / 30.6K | 0.0% | +| Roblox | 187 | 19 | 483,706.38 | 14.45 | 37.4K / 37.4K | 0.0% | + +> **Note:** [Warp][warp], [Packet][packet], [BridgeNet2][bridgenet2], and Roblox are partial rows. + +Conclusion: **Satset** wins this row. [QuickNet][quicknet] completes the full volume and sits near [ByteNet][bytenet], [Blink][blink], [NetrayCompile][netray-compile], and [Zap][zap]. ## SingleValue -| Library | Frames | Min FPS | Normalized | Drain | Sent / Received | Loss | +| Library | Frames | Min FPS | Kbps (p50) | Drain (s) | Sent / Received | Loss | | :--- | ---: | ---: | ---: | ---: | :--- | ---: | -| Satset | 601 | 60 | 2.46 | 3.43 | 120.2K / 120.2K | 0.0% | -| QuickNet | 600 | 60 | 2.39 | 3.75 | 120K / 120K | 0.0% | -| Warp | 600 | 60 | 2.29 | 3.70 | 120K / 120K | 0.0% | -| ByteNet | 600 | 60 | 2.41 | 3.43 | 120K / 120K | 0.0% | -| NetRay | 600 | 60 | 2.41 | 3.82 | 120K / 120K | 0.0% | -| Zap | 600 | 60 | 2.34 | 3.72 | 120K / 120K | 0.0% | -| Blink | 600 | 60 | 2.36 | 3.70 | 120K / 120K | 0.0% | -| Packet | 598 | 58 | 2.10 | 4.19 | 119.6K / 119.6K | 0.0% | -| BridgeNet2 | 600 | 60 | 145.18 | 10.40 | 120K / 120K | 0.0% | -| Roblox | 600 | 60 | 227.18 | 9.13 | 120K / 120K | 0.0% | - -This row is measurement-noise territory for compact serializers. +| **Satset** | 601 | 60 | 2.28 | 3.72 | 120.2K / 120.2K | 0.0% | +| [QuickNet][quicknet] | 601 | 60 | 2.43 | 3.68 | 120.2K / 120.2K | 0.0% | +| [Warp][warp] | 600 | 60 | 2.20 | 3.72 | 120K / 120K | 0.0% | +| [ByteNet][bytenet] | 600 | 60 | 2.41 | 3.72 | 120K / 120K | 0.0% | +| [NetrayCompile][netray-compile] | 599 | 60 | 2.42 | 3.72 | 119.8K / 119.8K | 0.0% | +| [Zap][zap] | 600 | 60 | 2.45 | 3.72 | 120K / 120K | 0.0% | +| [Blink][blink] | 600 | 60 | 2.43 | 3.72 | 120K / 120K | 0.0% | +| [Packet][packet] | 599 | 60 | 2.33 | 3.74 | 119.8K / 119.8K | 0.0% | +| [BridgeNet2][bridgenet2] | 600 | 60 | 144.94 | 11.35 | 120K / 120K | 0.0% | +| Roblox | 600 | 60 | 227.38 | 9.87 | 120K / 120K | 0.0% | + +> **Note:** [Packet][packet] and [NetrayCompile][netray-compile] are near full volume at 599 frames. This row is sensitive to Studio noise because the compact serializers are close. + +Conclusion: [Warp][warp] is the lowest row. **Satset** is second and about 3.8% higher than [Warp][warp]. ## Strings -| Library | Frames | Min FPS | Normalized | Drain | Sent / Received | Loss | +| Library | Frames | Min FPS | Kbps (p50) | Drain (s) | Sent / Received | Loss | | :--- | ---: | ---: | ---: | ---: | :--- | ---: | -| Satset | 601 | 60 | 96.43 | 9.75 | 120.2K / 120.2K | 0.0% | -| QuickNet | 600 | 60 | 96.38 | 9.82 | 120K / 120K | 0.0% | -| Warp | 600 | 60 | 3.45 | 3.77 | 120K / 120K | 0.0% | -| ByteNet | 7 | 0 | 0.00 | 3.01 | 1.4K / 1.4K | 0.0% | -| NetRay | 600 | 60 | 96.34 | 9.90 | 120K / 120K | 0.0% | -| Zap | 600 | 60 | 100.29 | 11.88 | 120K / 120K | 0.0% | -| Blink | 600 | 60 | 100.36 | 9.97 | 120K / 120K | 0.0% | -| Packet | 601 | 60 | 95.84 | 9.45 | 120.2K / 120.2K | 0.0% | -| BridgeNet2 | 600 | 60 | 27,188.84 | 39.92 | 120K / 120K | 0.0% | -| Roblox | 600 | 60 | 80,407.66 | 10.87 | 120K / 120K | 0.0% | - -Satset, QuickNet, NetRay, and Packet are close here. ByteNet's `0.00` row is incomplete and should not be read as free bandwidth. +| **Satset** | 601 | 60 | 3.28 | 4.18 | 120.2K / 120.2K | 0.0% | +| [QuickNet][quicknet] | 601 | 60 | 96.45 | 10.25 | 120.2K / 120.2K | 0.0% | +| [Warp][warp] | 600 | 60 | 15.87 | 6.25 | 120K / 120K | 0.0% | +| [ByteNet][bytenet] | 8 | 0 | 0.00 | 4.57 | 1.6K / 1.6K | 0.0% | +| [NetrayCompile][netray-compile] | 600 | 60 | 96.61 | 10.40 | 120K / 120K | 0.0% | +| [Zap][zap] | 597 | 57 | 100.30 | 10.32 | 119.4K / 119.4K | 0.0% | +| [Blink][blink] | 600 | 60 | 100.56 | 10.18 | 120K / 120K | 0.0% | +| [Packet][packet] | 599 | 60 | 95.68 | 10.12 | 119.8K / 119.8K | 0.0% | +| [BridgeNet2][bridgenet2] | 600 | 60 | 27,248.36 | 24.10 | 120K / 120K | 0.0% | +| Roblox | 599 | 59 | 94,127.50 | 11.33 | 119.8K / 119.8K | 0.0% | + +> **Note:** [ByteNet][bytenet] went down over-capacity in the String test, so its `0.00` Kbps row is invalid. + +Conclusion: **Satset** wins the valid rows. [Warp][warp] is second. --- @@ -156,11 +178,22 @@ Satset, QuickNet, NetRay, and Packet are close here. ByteNet's `0.00` row is inc - Environment: local Roblox Studio, 1 player. - Test load: 200 events per frame for 10 seconds per library per payload. - Payload source: `benchmark/src/shared/benches`. -- Satset config source: `benchmark/src/shared/BenchmarkConfig.luau`. -- Satset benchmark control: `reliableThreshold = 0`, `maxPacketsPerFrame = 0`. +- Mode source: `benchmark/src/shared/modes`. +- **Satset** config source: `benchmark/src/shared/BenchmarkConfig.luau`. - Validation: the server verifies the first received payload for each library and counts sent/received volume. -The benchmark does not prove a single universal winner. It shows how each library behaves under one heavy local Studio workload. Use frame counts, min FPS, and packet counts before comparing bandwidth. +The benchmark does not prove one universal winner. It shows how each library behaves under one heavy local Studio workload. Check frame counts, min FPS, and packet counts before comparing bandwidth. + +## Source Links + +- [Warp][warp] +- [ByteNet][bytenet] +- [BridgeNet2][bridgenet2] +- [QuickNet][quicknet] +- [NetrayCompile][netray-compile] +- [Zap][zap] +- [Blink][blink] +- [Packet][packet] ## Raw Data @@ -175,3 +208,12 @@ The benchmark place file is [satset-benchmark.rbxl](./satset-benchmark.rbxl). 3. Press Play with one local player and wait until Studio Output prints `Generated results`. 4. The server creates `game.Result`, a `StringValue` whose `Value` contains the JSON for the run. 5. Put that JSON in `benchmark/benchmark-result.json`. + +[warp]: https://devforum.roblox.com/t/warp-very-fast-powerful-networking-library/2779813 +[bytenet]: https://github.com/ffrostfall/ByteNet +[bridgenet2]: https://github.com/ffrostfall/BridgeNet2 +[quicknet]: https://devforum.roblox.com/t/quicknet-v030-up-to-10x-faster-than-remoteevents-drop-in-networking-library/4624342/1 +[netray-compile]: https://devforum.roblox.com/t/netray-compile-idl-compiler-v011/4348861 +[zap]: https://zap.redblox.dev +[blink]: https://devforum.roblox.com/t/blink-an-idl-compiler-written-in-luau-for-roblox-buffer-networking-0185/2959671 +[packet]: https://devforum.roblox.com/t/packet-networking-library/3573907/1 diff --git a/benchmark/benchmark-result.json b/benchmark/benchmark-result.json index eb49765..102b11a 100644 --- a/benchmark/benchmark-result.json +++ b/benchmark/benchmark-result.json @@ -3,276 +3,298 @@ "warp": { "Framerate": [ 60, - 54, + 60, 60, 61, 61, 61 ], "Bandwidth": [ - 2.795123338699341, - 2.1197614669799806, - 3.6373419761657717, - 4.114903926849365, - 4.114903926849365, - 4.114903926849365 - ], - "DrainDuration": 4.939354101999925, - "Recieve": 112800, - "Sent": 112800, - "Frames": 564, + 4.0277838706970219, + 2.7498388290405275, + 6.410828590393066, + 9.12809944152832, + 9.12809944152832, + 9.12809944152832 + ], + "DrainDuration": 4.413603590001003, + "Recieve": 120000, + "Sent": 120000, + "Frames": 600, "NormalizedBandwidth": [ - 2.8061359603857296, - 2.119761577534156, - 4.058330842473024, - 4.132049569632617, - 4.132049569632617, - 4.132049569632617 + 4.02778408076232, + 2.749838972455798, + 6.384555038644333, + 9.1280999090946, + 9.1280999090946, + 9.1280999090946 ] }, "bridgenet2": { "Framerate": [ - 60, - 56, + 59, + 55, 60, 60, 60, 60 ], "Bandwidth": [ - 15633.830078125, - 11280.8828125, - 15722.05859375, - 15730.8095703125, - 15730.8095703125, - 15730.8095703125 - ], - "DrainDuration": 22.1529455660002, - "Recieve": 115200, - "Sent": 115200, - "Frames": 576, + 14499.1328125, + 8325.6943359375, + 15338.3291015625, + 15500.03515625, + 15500.03515625, + 15500.03515625 + ], + "DrainDuration": 23.750724323999749, + "Recieve": 91000, + "Sent": 91000, + "Frames": 455, "NormalizedBandwidth": [ - 15633.830893492777, - 11280.883400843886, - 15722.059413719253, - 15730.810390738152, - 15730.810390738152, - 15730.810390738152 + 14744.881595276645, + 9612.393061275623, + 15500.035964639828, + 16118.14186215539, + 16118.14186215539, + 16118.14186215539 ] }, - "packet": { + "quicknet": { "Framerate": [ 60, 60, 60, 60, - 60, - 60 + 61, + 61 ], "Bandwidth": [ - 72.64115142822266, - 55.15867233276367, - 72.71024322509766, - 72.74811553955078, - 72.80789947509766, - 72.80789947509766 - ], - "DrainDuration": 9.266659813999923, + 38.753746032714847, + 30.02865982055664, + 38.849117279052737, + 39.132835388183597, + 39.1534538269043, + 39.1534538269043 + ], + "DrainDuration": 8.282944889997453, "Recieve": 120200, "Sent": 120200, "Frames": 601, "NormalizedBandwidth": [ - 72.64115521675393, - 55.15867520951261, - 72.71024701723235, - 72.74811933366067, - 72.80790327232552, - 72.80790327232552 + 38.7537480538802, + 30.028661386673293, + 38.992990903780079, + 38.99439649284177, + 39.13283742912, + 39.13283742912 ] }, - "bytenet": { + "zap": { "Framerate": [ 60, + 57, 60, - 61, - 61, - 61, - 61 + 60, + 60, + 60 ], "Bandwidth": [ - 72.50541687011719, - 50.04075622558594, - 72.63225555419922, - 72.88240051269531, - 72.88240051269531, - 72.88240051269531 - ], - "DrainDuration": 9.317384534999747, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 72.13491821289063, + 30.292875289916993, + 72.80009460449219, + 73.20547485351563, + 73.20547485351563, + 73.20547485351563 + ], + "DrainDuration": 10.683616649002943, + "Recieve": 114000, + "Sent": 114000, + "Frames": 570, "NormalizedBandwidth": [ - 72.50542065156935, - 50.04075883541475, - 72.7114399665677, - 72.81098939238808, - 72.81098939238808, - 72.81098939238808 + 72.13492197501978, + 31.887238840027359, + 72.800098401313, + 73.20547867147866, + 73.20547867147866, + 73.20547867147866 ] }, "roblox": { "Framerate": [ 60, 60, - 60, + 61, 61, 61, 61 ], "Bandwidth": [ - 84985.6953125, - 54201.9375, - 103543.6484375, - 138999.15625, - 138999.15625, - 138999.15625 - ], - "DrainDuration": 9.916805902000306, + 80782.203125, + 62852.0078125, + 97348.0234375, + 97746.6875, + 97746.6875, + 97746.6875 + ], + "DrainDuration": 10.384693643998617, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 84985.69974484941, - 54201.94032685133, - 103543.65383772209, - 138999.16349937093, - 138999.16349937093, - 138999.16349937093 + 80782.2073381202, + 63113.8944407636, + 96949.0612292053, + 97746.69259788702, + 97746.69259788702, + 97746.69259788702 ] }, "satset": { - "DrainDuration": 7.816942086999916, + "DrainDuration": 3.7155876220022039, "DebugStats": { - "reliableCommitBytes": 72601200, - "commitGcKb": 118, + "exactBufferPoolAcceptedReleases": 601, "unreliableCommits": 0, "receiveDecodeGcKb": 0, - "commitGcSamples": 600, - "reliableCommits": 600, - "packetDecodeGcKb": 0, + "groupedRuns": 601, + "exactBufferPoolRejectedBySizeCap": 0, + "reliableCommits": 601, + "deltaSkipped": 1, "listenerCallGcSamples": 0, "packetDecodeGcSamples": 0, "exactBufferPoolRejectedBySizeClassCap": 0, "streamGrowths": 1, - "streamGrowthBytes": 131072, + "exactBufferPoolHits": 600, + "adapterGcSamples": 120200, + "sendGcSamples": 240401, + "listenerDispatchGcKb": 0, + "exactBufferPoolBytes": 120406, + "adapterGcKb": 9391, "exactBufferPoolSizeClasses": 1, - "exactBufferPoolHits": 599, - "adapterGcSamples": 120000, - "sendGcSamples": 240001, + "reliableCommitBytes": 72364006, + "commitGcKb": 117, + "receiveDecodeGcSamples": 0, + "commitGcSamples": 601, + "packetDecodeGcKb": 0, + "streamGrowthBytes": 131072, + "groupedPackets": 120200, + "exactBufferPoolMisses": 1, + "deltaZeroBytes": 72242400, "listenerDispatchGcSamples": 0, - "listenerDispatchGcKb": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolAcceptedReleases": 600, - "exactBufferPoolRejectedBySizeCap": 0, + "deltaEncoded": 600, + "deltaDecoded": 0, + "groupedHeaderBytesSaved": 358196, + "groupedDecodeBytes": 0, "exactBufferPoolRejectedByPerSizeCap": 0, - "exactBufferPoolMisses": 1, - "exactBufferPoolBytes": 121002, - "adapterGcKb": -1922, - "sendGcKb": 129, + "ungroupedPackets": 0, + "deltaStoredBytes": 72364006, + "exactBufferPoolRejectedByTotalByteCap": 0, + "sendGcKb": 128, "unreliableCommitBytes": 0, - "listenerCallGcKb": 0, - "receiveDecodeGcSamples": 0 + "deltaInputBytes": 72364006, + "listenerCallGcKb": 0 }, - "Sent": 120000, - "Frames": 600, + "Sent": 120200, + "Frames": 601, "NormalizedBandwidth": [ - 39.01645100947576, - 29.32838974370597, - 39.08814330622268, - 39.11245550056677, - 39.12745870569495, - 39.12745870569495 + 2.5084127303875105, + 2.000760421196537, + 2.5231975440858447, + 2.526787412818603, + 2.526787412818603, + 2.526787412818603 ], "Framerate": [ + 61, 60, - 59, - 60, - 60, + 61, + 61, 61, 61 ], "Bandwidth": [ - 38.99591064453125, - 29.328388214111329, - 39.05757141113281, - 39.11245346069336, - 39.12745666503906, - 39.12745666503906 + 2.5084125995635988, + 2.000760316848755, + 2.5231974124908449, + 2.526787281036377, + 2.526787281036377, + 2.526787281036377 ], "ServerDebugStats": { - "reliableCommitBytes": 0, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 0, "unreliableCommits": 0, - "listenerDispatchGcSamples": 0, + "receiveDecodeGcKb": 1, + "groupedRuns": 0, "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 0, - "listenerCallGcKb": -4238, - "listenerCallGcSamples": 120000, - "packetDecodeGcSamples": 120000, + "deltaSkipped": 1, + "listenerCallGcSamples": 120200, + "packetDecodeGcSamples": 120200, "exactBufferPoolRejectedBySizeClassCap": 0, - "streamGrowths": 0, - "streamGrowthBytes": 0, - "receiveDecodeGcSamples": 120600, + "deltaEncoded": 0, "exactBufferPoolHits": 0, "adapterGcSamples": 0, "sendGcSamples": 0, - "commitGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolAcceptedReleases": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, - "packetDecodeGcKb": 202416, "exactBufferPoolBytes": 0, "adapterGcKb": 0, "exactBufferPoolSizeClasses": 0, - "unreliableCommitBytes": 0, + "reliableCommitBytes": 0, + "commitGcKb": 0, + "receiveDecodeGcSamples": 120801, + "commitGcSamples": 0, + "listenerCallGcKb": -3571, + "streamGrowthBytes": 0, + "deltaZeroBytes": 0, + "exactBufferPoolMisses": 0, + "groupedDecodeBytes": 72360400, + "groupedPackets": 0, + "listenerDispatchGcSamples": 0, + "deltaDecoded": 600, + "groupedHeaderBytesSaved": 0, + "ungroupedPackets": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolRejectedByTotalByteCap": 0, + "deltaStoredBytes": 72364006, + "packetDecodeGcKb": 202826, "sendGcKb": 0, - "receiveDecodeGcKb": 1 + "unreliableCommitBytes": 0, + "deltaInputBytes": 0, + "streamGrowths": 0 }, - "Recieve": 120000 + "Recieve": 120200 }, - "zap": { + "bytenet": { "Framerate": [ 60, - 57, - 61, + 60, + 60, 61, 61, 61 ], "Bandwidth": [ - 72.82897186279297, - 53.49580764770508, - 72.90431213378906, - 72.99083709716797, - 72.99083709716797, - 72.99083709716797 - ], - "DrainDuration": 9.366167209000196, - "Recieve": 119400, - "Sent": 119400, - "Frames": 597, + 72.61592102050781, + 54.16200256347656, + 72.90460205078125, + 73.01457214355469, + 73.01457214355469, + 73.01457214355469 + ], + "DrainDuration": 9.78345131199967, + "Recieve": 120000, + "Sent": 120000, + "Frames": 600, "NormalizedBandwidth": [ - 72.82897566111984, - 53.49581043772886, - 72.99084090393677, - 75.75096898770812, - 75.75096898770812, - 75.75096898770812 + 72.61592480772322, + 54.16200538824512, + 72.90460585305255, + 73.01457595156137, + 73.01457595156137, + 73.01457595156137 ] }, - "netray_compile": { + "blink": { "Framerate": [ 60, 60, @@ -282,27 +304,27 @@ 60 ], "Bandwidth": [ - 72.57927703857422, - 53.587520599365237, - 72.74488067626953, - 72.81295776367188, - 72.82084655761719, - 72.82084655761719 - ], - "DrainDuration": 9.333106287000192, + 72.70699310302735, + 53.676841735839847, + 72.97962188720703, + 73.0245590209961, + 73.0634765625, + 73.0634765625 + ], + "DrainDuration": 10.349575274998643, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 72.57928082387849, - 53.58752339417222, - 72.7448844702107, - 72.81296156116355, - 72.82085035552029, - 72.82085035552029 + 72.70699689499253, + 53.67684453530529, + 72.97962569339092, + 73.02456282952363, + 73.06348037305725, + 73.06348037305725 ] }, - "blink": { + "packet": { "Framerate": [ 60, 60, @@ -312,27 +334,27 @@ 60 ], "Bandwidth": [ - 72.62796020507813, - 53.81346130371094, - 72.70398712158203, - 72.70944213867188, - 72.8438720703125, - 72.8438720703125 - ], - "DrainDuration": 9.350005999000132, + 72.40576934814453, + 53.337188720703128, + 72.773681640625, + 72.78375244140625, + 72.80750274658203, + 72.80750274658203 + ], + "DrainDuration": 9.83177207100016, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 72.62796399292142, - 53.81346411030165, - 72.70399091339044, - 72.70944593076479, - 72.84387586941648, - 72.84387586941648 + 72.40577312439967, + 53.33719150245429, + 72.77368543606826, + 72.78375623737475, + 72.8075065437892, + 72.8075065437892 ] }, - "quicknet": { + "netray_compile": { "Framerate": [ 60, 60, @@ -342,24 +364,24 @@ 60 ], "Bandwidth": [ - 38.97089767456055, - 29.025203704833986, - 38.99586868286133, - 39.01387023925781, - 39.06427001953125, - 39.06427001953125 - ], - "DrainDuration": 8.800044874999913, + 72.48750305175781, + 53.29486846923828, + 72.67902374267578, + 72.68572235107422, + 72.9260482788086, + 72.9260482788086 + ], + "DrainDuration": 9.832364068999596, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 38.970899707051248, - 29.02520521861632, - 38.995870716654369, - 39.013872273989708, - 39.064272056891699, - 39.064272056891699 + 72.4875068322757, + 53.29487124878227, + 72.67902753318225, + 72.68572614193005, + 72.9260520821984, + 72.9260520821984 ] } }, @@ -374,188 +396,199 @@ 60 ], "Bandwidth": [ - 2.5221173763275148, - 2.368652582168579, - 2.7293598651885988, - 2.742267608642578, - 4.425382137298584, - 4.425382137298584 - ], - "DrainDuration": 3.73334475799993, + 2.9475290775299074, + 2.466196060180664, + 3.549906015396118, + 3.5811688899993898, + 4.849908828735352, + 4.849908828735352 + ], + "DrainDuration": 4.317473862000043, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 2.522117507866186, - 2.368652705703438, - 2.729360007535808, - 2.742267751662979, - 4.425382368100248, - 4.425382368100248 + 2.947529231255528, + 2.4661961888028118, + 3.5499062005381445, + 3.5811690767719019, + 4.849909081677808, + 4.849909081677808 ] }, "bridgenet2": { "Framerate": [ - 59, - 57, + 60, + 53, 60, 60, 60, 60 ], "Bandwidth": [ - 23483.62109375, - 17632.859375, - 23723.904296875, - 23853.603515625, - 23853.603515625, - 23853.603515625 - ], - "DrainDuration": 23.136419982999997, - "Recieve": 116200, - "Sent": 116200, - "Frames": 581, + 23892.076171875, + 18846.15234375, + 24106.646484375, + 24169.123046875, + 24169.123046875, + 24169.123046875 + ], + "DrainDuration": 26.699889638999595, + "Recieve": 116400, + "Sent": 116400, + "Frames": 582, "NormalizedBandwidth": [ - 23921.604724863195, - 18792.91683245423, - 24179.78737920204, - 24180.170360287975, - 24180.170360287975, - 24180.170360287975 + 24010.09304909887, + 19081.730287116225, + 24169.124352411494, + 25376.27131777373, + 25376.27131777373, + 25376.27131777373 ] }, - "packet": { + "quicknet": { "Framerate": [ 60, - 60, + 58, 61, 61, 61, 61 ], "Bandwidth": [ - 23.585262298583986, - 17.192241668701173, - 23.95997428894043, - 24.088584899902345, - 24.088584899902345, - 24.088584899902345 - ], - "DrainDuration": 6.731871806000072, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 10.153005599975586, + 8.104384422302246, + 10.225139617919922, + 10.230781555175782, + 10.230781555175782, + 10.230781555175782 + ], + "DrainDuration": 5.717019517000153, + "Recieve": 114200, + "Sent": 114200, + "Frames": 571, "NormalizedBandwidth": [ - 23.627085793863999, - 17.335511397494707, - 23.959975717066187, - 24.088586470299349, - 24.088586470299349, - 24.088586470299349 + 10.153006129496092, + 8.104384844978832, + 10.22514015120251, + 10.230782088752619, + 10.230782088752619, + 10.230782088752619 ] }, - "bytenet": { + "zap": { "Framerate": [ 60, 60, 60, - 61, - 61, - 61 + 60, + 60, + 60 ], "Bandwidth": [ - 18.982406616210939, - 14.272871017456055, - 19.024717330932618, - 19.0316219329834, - 19.0316219329834, - 19.0316219329834 - ], - "DrainDuration": 6.267633172999922, + 29.38677406311035, + 22.137832641601564, + 29.427814483642579, + 29.499996185302736, + 29.509511947631837, + 29.509511947631837 + ], + "DrainDuration": 7.835103571000218, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 18.98240760622059, - 14.272871761844286, - 19.024718323148947, - 19.03162292555983, - 19.03162292555983, - 19.03162292555983 + 29.386775595750053, + 22.137833796179508, + 29.427816018422705, + 29.49999772384743, + 29.509513486672817, + 29.509513486672817 ] }, "roblox": { "Framerate": [ - 54, + 57, 53, - 54, - 55, - 55, - 55 + 58, + 58, + 58, + 58 ], "Bandwidth": [ - 52951.83203125, - 42563.93359375, - 53619.45703125, - 54912.875, - 54912.875, - 54912.875 - ], - "DrainDuration": 10.33922282399999, - "Recieve": 106600, - "Sent": 106600, - "Frames": 533, + 49125.4921875, + 30933.96875, + 51720.6796875, + 52392.53125, + 52392.53125, + 52392.53125 + ], + "DrainDuration": 11.612133825001365, + "Recieve": 111800, + "Sent": 111800, + "Frames": 559, "NormalizedBandwidth": [ - 59674.45842593734, - 48587.1337841858, - 60321.8916681039, - 62165.521299562875, - 62165.521299562875, - 62165.521299562875 + 50833.58623481181, + 33886.75823918288, + 55150.03546286038, + 56797.538339580795, + 56797.538339580795, + 56797.538339580795 ] }, "satset": { - "DrainDuration": 5.217620056000214, + "DrainDuration": 3.7005556200019784, "DebugStats": { - "reliableCommitBytes": 15627202, - "commitGcKb": 25, + "exactBufferPoolAcceptedReleases": 601, "unreliableCommits": 0, "receiveDecodeGcKb": 0, - "commitGcSamples": 601, + "groupedRuns": 601, + "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 601, - "packetDecodeGcKb": 0, + "deltaSkipped": 1, "listenerCallGcSamples": 0, "packetDecodeGcSamples": 0, "exactBufferPoolRejectedBySizeClassCap": 0, "streamGrowths": 0, - "streamGrowthBytes": 0, - "exactBufferPoolSizeClasses": 2, "exactBufferPoolHits": 600, "adapterGcSamples": 120200, "sendGcSamples": 240400, - "listenerDispatchGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolAcceptedReleases": 601, - "exactBufferPoolRejectedBySizeCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolBytes": 145812, + "adapterGcKb": 9392, + "exactBufferPoolSizeClasses": 2, + "reliableCommitBytes": 15269006, + "commitGcKb": 26, + "receiveDecodeGcSamples": 0, + "commitGcSamples": 601, + "packetDecodeGcKb": 0, + "streamGrowthBytes": 0, + "groupedPackets": 120200, "exactBufferPoolMisses": 1, - "exactBufferPoolBytes": 147004, - "adapterGcKb": 9391, + "deltaZeroBytes": 15242400, + "listenerDispatchGcSamples": 0, + "deltaEncoded": 600, + "deltaDecoded": 0, + "groupedHeaderBytesSaved": 358196, + "groupedDecodeBytes": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "ungroupedPackets": 0, + "deltaStoredBytes": 15269006, + "exactBufferPoolRejectedByTotalByteCap": 0, "sendGcKb": 1, "unreliableCommitBytes": 0, - "listenerCallGcKb": 0, - "receiveDecodeGcSamples": 0 + "deltaInputBytes": 15269006, + "listenerCallGcKb": 0 }, "Sent": 120200, "Frames": 601, "NormalizedBandwidth": [ - 10.341739240230404, - 7.609570900104892, - 10.370192114944317, - 10.385531967123747, - 10.388734405645259, - 10.388734405645259 + 2.360639933679094, + 1.8727999710261756, + 2.4060102763926318, + 2.4258660151143465, + 2.4308239297253388, + 2.4308239297253388 ], "Framerate": [ 60, @@ -566,77 +599,58 @@ 60 ], "Bandwidth": [ - 10.3417387008667, - 7.609570503234863, - 10.37019157409668, - 10.385531425476075, - 10.388733863830567, - 10.388733863830567 + 2.360639810562134, + 1.8727998733520508, + 2.406010150909424, + 2.425865888595581, + 2.430823802947998, + 2.430823802947998 ], "ServerDebugStats": { - "reliableCommitBytes": 0, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 0, "unreliableCommits": 0, - "listenerDispatchGcSamples": 0, + "receiveDecodeGcKb": 0, + "groupedRuns": 0, "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 0, - "listenerCallGcKb": -1098686, + "deltaSkipped": 1, "listenerCallGcSamples": 120200, "packetDecodeGcSamples": 120200, "exactBufferPoolRejectedBySizeClassCap": 0, - "streamGrowths": 0, - "streamGrowthBytes": 0, - "receiveDecodeGcSamples": 120801, + "deltaEncoded": 0, "exactBufferPoolHits": 0, "adapterGcSamples": 0, "sendGcSamples": 0, - "commitGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolAcceptedReleases": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, - "packetDecodeGcKb": 1085444, "exactBufferPoolBytes": 0, "adapterGcKb": 0, "exactBufferPoolSizeClasses": 0, - "unreliableCommitBytes": 0, + "reliableCommitBytes": 0, + "commitGcKb": 0, + "receiveDecodeGcSamples": 120801, + "commitGcSamples": 0, + "listenerCallGcKb": -1073492, + "streamGrowthBytes": 0, + "deltaZeroBytes": 0, + "exactBufferPoolMisses": 0, + "groupedDecodeBytes": 15265400, + "groupedPackets": 0, + "listenerDispatchGcSamples": 0, + "deltaDecoded": 600, + "groupedHeaderBytesSaved": 0, + "ungroupedPackets": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolRejectedByTotalByteCap": 0, + "deltaStoredBytes": 15269006, + "packetDecodeGcKb": 1060436, "sendGcKb": 0, - "receiveDecodeGcKb": 0 + "unreliableCommitBytes": 0, + "deltaInputBytes": 0, + "streamGrowths": 0 }, "Recieve": 120200 }, - "zap": { - "Framerate": [ - 60, - 58, - 60, - 61, - 61, - 61 - ], - "Bandwidth": [ - 29.29340171813965, - 22.247533798217775, - 29.353622436523439, - 29.4331111907959, - 29.4331111907959, - 29.4331111907959 - ], - "DrainDuration": 7.249783056000069, - "Recieve": 118200, - "Sent": 118200, - "Frames": 591, - "NormalizedBandwidth": [ - 29.25287780739467, - 22.24753495851708, - 29.353623967434147, - 29.43311272585227, - 29.43311272585227, - 29.43311272585227 - ] - }, - "netray_compile": { + "bytenet": { "Framerate": [ 60, 60, @@ -646,57 +660,87 @@ 60 ], "Bandwidth": [ - 10.330069541931153, - 7.806988716125488, - 10.423501014709473, - 10.453253746032715, - 10.481480598449707, - 10.481480598449707 - ], - "DrainDuration": 5.266017387999909, + 19.031147003173829, + 14.045348167419434, + 19.05061912536621, + 19.091167449951173, + 19.112390518188478, + 19.112390518188478 + ], + "DrainDuration": 6.766314290998707, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 10.330070080686263, - 7.806989123291679, - 10.423501558337414, - 10.453254291212382, - 10.481481145101519, - 10.481481145101519 + 19.03114799572549, + 14.045348899941424, + 19.050620118933425, + 19.091168445633146, + 19.11239151497732, + 19.11239151497732 ] }, "blink": { "Framerate": [ 60, - 60, + 57, 60, 60, 60, 60 ], "Bandwidth": [ - 29.3564510345459, - 22.068872451782228, - 29.416034698486329, - 29.475997924804689, - 29.538347244262697, - 29.538347244262697 - ], - "DrainDuration": 7.3002381010001049, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 28.86579704284668, + 17.153650283813478, + 29.548643112182618, + 29.582765579223634, + 29.582765579223634, + 29.582765579223634 + ], + "DrainDuration": 8.015951117999066, + "Recieve": 107400, + "Sent": 107400, + "Frames": 537, "NormalizedBandwidth": [ - 29.35645256560413, - 22.068873602763618, - 29.41603623265209, - 29.475999462097776, - 29.538348784807554, - 29.538348784807554 + 28.86579854831531, + 18.056474933088258, + 29.548644653264448, + 29.582767122085089, + 29.582767122085089, + 29.582767122085089 ] }, - "quicknet": { + "packet": { + "Framerate": [ + 60, + 54, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 29.18402862548828, + 9.837071418762207, + 29.433975219726564, + 29.61518669128418, + 29.61518669128418, + 29.61518669128418 + ], + "DrainDuration": 7.699587213002815, + "Recieve": 111800, + "Sent": 111800, + "Frames": 559, + "NormalizedBandwidth": [ + 29.300832443054369, + 10.930079944587149, + 29.433976754827996, + 29.615188235836528, + 29.615188235836528, + 29.615188235836528 + ] + }, + "netray_compile": { "Framerate": [ 60, 60, @@ -706,24 +750,24 @@ 60 ], "Bandwidth": [ - 10.251404762268067, - 7.644955158233643, - 10.285540580749512, - 10.343138694763184, - 10.350224494934082, - 10.350224494934082 - ], - "DrainDuration": 5.349255260000064, + 10.284943580627442, + 7.487804412841797, + 10.344758987426758, + 10.370344161987305, + 10.395271301269532, + 10.395271301269532 + ], + "DrainDuration": 5.749783165003464, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 10.251405296920489, - 7.6449555569491249, - 10.285541117182256, - 10.343139234199903, - 10.350225034740355, - 10.350225034740355 + 10.284944117029049, + 7.487804803361229, + 10.344759526947982, + 10.3703447028429, + 10.395271843425178, + 10.395271843425178 ] } }, @@ -732,63 +776,63 @@ "Framerate": [ 60, 60, - 61, - 61, - 61, - 61 + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 2.3952059745788576, - 1.8949764966964722, - 2.4854116439819338, - 2.4946699142456056, - 2.4946699142456056, - 2.4946699142456056 - ], - "DrainDuration": 3.5164384939998856, + 2.2392513751983644, + 1.867808222770691, + 2.2660293579101564, + 2.2774643898010256, + 2.300159454345703, + 2.300159454345703 + ], + "DrainDuration": 3.699181381998642, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 2.3952060994985837, - 1.894976595527198, - 2.475225660918384, - 2.4946700443527805, - 2.4946700443527805, - 2.4946700443527805 + 2.239251491984424, + 1.8755908539821503, + 2.266029476092797, + 2.2774645085800495, + 2.300159574308367, + 2.300159574308367 ] }, "bridgenet2": { "Framerate": [ - 60, - 60, + 57, + 50, 60, 60, 60, 60 ], "Bandwidth": [ - 1680.6226806640626, - 1335.4205322265626, - 1682.36572265625, - 1684.0098876953126, - 1684.0745849609376, - 1684.0745849609376 - ], - "DrainDuration": 16.39816557999984, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 1189.472900390625, + 296.58770751953127, + 1474.7294921875, + 1678.9439697265626, + 1678.9439697265626, + 1678.9439697265626 + ], + "DrainDuration": 20.53383760399811, + "Recieve": 77600, + "Sent": 77600, + "Frames": 388, "NormalizedBandwidth": [ - 1680.6227683153657, - 1335.4206018741707, - 1682.3658103984599, - 1684.0099755232723, - 1684.0746727922715, - 1684.0746727922715 + 1252.0768031372236, + 330.9150063548211, + 1474.7295691006366, + 1678.944057290314, + 1678.944057290314, + 1678.944057290314 ] }, - "packet": { + "quicknet": { "Framerate": [ 60, 60, @@ -798,179 +842,201 @@ 61 ], "Bandwidth": [ - 4.826046466827393, - 3.839698553085327, - 4.86691427230835, - 4.877917289733887, - 4.877917289733887, - 4.877917289733887 - ], - "DrainDuration": 4.2670023149999, - "Recieve": 120200, - "Sent": 120200, - "Frames": 601, + 4.166211128234863, + 3.324403762817383, + 4.224857807159424, + 4.229468822479248, + 4.229468822479248, + 4.229468822479248 + ], + "DrainDuration": 4.198859246000211, + "Recieve": 120000, + "Sent": 120000, + "Frames": 600, "NormalizedBandwidth": [ - 4.836130584914796, - 3.839698753341212, - 4.869125389413176, - 4.877917544137098, - 4.877917544137098, - 4.877917544137098 + 4.166211353279879, + 3.324403945486827, + 4.229469039124236, + 4.242173025224228, + 4.242173025224228, + 4.242173025224228 ] }, - "bytenet": { + "zap": { "Framerate": [ - 60, - 58, 60, 60, - 60, - 60 + 61, + 61, + 61, + 61 ], "Bandwidth": [ - 4.965731143951416, - 3.7711398601531984, - 5.00945520401001, - 5.052359104156494, - 5.070005893707275, - 5.070005893707275 - ], - "DrainDuration": 4.249917537999863, - "Recieve": 119600, - "Sent": 119600, - "Frames": 598, + 4.966148376464844, + 3.8344080448150636, + 5.0123982429504398, + 5.020265102386475, + 5.020265102386475, + 5.020265102386475 + ], + "DrainDuration": 4.735470069997973, + "Recieve": 120000, + "Sent": 120000, + "Frames": 600, "NormalizedBandwidth": [ - 4.965731402934477, - 3.9011793764045836, - 5.009455465273458, - 5.052359367657555, - 5.070006158128688, - 5.070006158128688 + 4.966148635469665, + 3.8344082447950269, + 5.012398504367379, + 5.020265364213703, + 5.020265364213703, + 5.020265364213703 ] }, "roblox": { "Framerate": [ 60, + 50, 60, - 60, - 60, - 60, - 60 + 61, + 61, + 61 ], "Bandwidth": [ - 3019.898193359375, - 2986.68310546875, - 3056.64501953125, - 3117.58544921875, - 3235.4482421875, - 3235.4482421875 - ], - "DrainDuration": 9.332867358000386, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 2902.16357421875, + 2823.098388671875, + 3018.50439453125, + 3067.470703125, + 3067.470703125, + 3067.470703125 + ], + "DrainDuration": 9.704893337999238, + "Recieve": 100000, + "Sent": 100000, + "Frames": 500, "NormalizedBandwidth": [ - 3019.898350859339, - 2986.6832612364125, - 3056.6451789477107, - 3117.5856118135018, - 3235.4484109292754, - 3235.4484109292754 + 2976.2074306119304, + 2825.0282235085708, + 3284.566564299101, + 3711.6397512146688, + 3711.6397512146688, + 3711.6397512146688 ] }, "satset": { - "DrainDuration": 4.232817628000248, + "DrainDuration": 3.686376380999718, "DebugStats": { - "reliableCommitBytes": 3841200, - "commitGcKb": 6, + "exactBufferPoolAcceptedReleases": 601, "unreliableCommits": 0, "receiveDecodeGcKb": 0, - "commitGcSamples": 600, - "reliableCommits": 600, - "packetDecodeGcKb": 0, + "groupedRuns": 601, + "exactBufferPoolRejectedBySizeCap": 0, + "reliableCommits": 601, + "deltaSkipped": 1, "listenerCallGcSamples": 0, "packetDecodeGcSamples": 0, "exactBufferPoolRejectedBySizeClassCap": 0, "streamGrowths": 0, - "streamGrowthBytes": 0, + "exactBufferPoolHits": 600, + "adapterGcSamples": 120200, + "sendGcSamples": 240400, + "listenerDispatchGcKb": 0, + "exactBufferPoolBytes": 151618, + "adapterGcKb": 0, "exactBufferPoolSizeClasses": 3, - "exactBufferPoolHits": 599, - "adapterGcSamples": 120000, - "sendGcSamples": 240000, + "reliableCommitBytes": 3489406, + "commitGcKb": 6, + "receiveDecodeGcSamples": 0, + "commitGcSamples": 601, + "packetDecodeGcKb": 0, + "streamGrowthBytes": 0, + "groupedPackets": 120200, + "exactBufferPoolMisses": 1, + "deltaZeroBytes": 3482400, "listenerDispatchGcSamples": 0, - "listenerDispatchGcKb": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolAcceptedReleases": 600, - "exactBufferPoolRejectedBySizeCap": 0, + "deltaEncoded": 600, + "deltaDecoded": 0, + "groupedHeaderBytesSaved": 358196, + "groupedDecodeBytes": 0, "exactBufferPoolRejectedByPerSizeCap": 0, - "exactBufferPoolMisses": 1, - "exactBufferPoolBytes": 153406, - "adapterGcKb": 0, + "ungroupedPackets": 0, + "deltaStoredBytes": 3489406, + "exactBufferPoolRejectedByTotalByteCap": 0, "sendGcKb": 1, "unreliableCommitBytes": 0, - "listenerCallGcKb": 0, - "receiveDecodeGcSamples": 0 + "deltaInputBytes": 3489406, + "listenerCallGcKb": 0 }, - "Sent": 120000, - "Frames": 600, + "Sent": 120200, + "Frames": 601, "NormalizedBandwidth": [ - 4.341675031123486, - 3.3954738957309429, - 4.397094955888861, - 4.414083234210171, - 4.414083234210171, - 4.414083234210171 + 2.3193463169424097, + 1.8469095018448645, + 2.3516906578075615, + 2.3878833086542556, + 2.3935915265674958, + 2.3935915265674958 ], "Framerate": [ 60, 60, - 61, - 61, - 61, - 61 + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 4.3416748046875, - 3.3954737186431886, - 4.3970947265625, - 4.414083003997803, - 4.414083003997803, - 4.414083003997803 + 2.3193461894989015, + 1.8469094038009644, + 2.3516905307769777, + 2.387883186340332, + 2.3935914039611818, + 2.3935914039611818 ], "ServerDebugStats": { - "reliableCommitBytes": 0, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 0, "unreliableCommits": 0, - "listenerDispatchGcSamples": 0, + "receiveDecodeGcKb": 2, + "groupedRuns": 0, "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 0, - "listenerCallGcKb": -1815, - "listenerCallGcSamples": 120000, - "packetDecodeGcSamples": 120000, + "deltaSkipped": 1, + "listenerCallGcSamples": 120200, + "packetDecodeGcSamples": 120200, "exactBufferPoolRejectedBySizeClassCap": 0, - "streamGrowths": 0, - "streamGrowthBytes": 0, - "receiveDecodeGcSamples": 120600, + "deltaEncoded": 0, "exactBufferPoolHits": 0, "adapterGcSamples": 0, "sendGcSamples": 0, - "commitGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolAcceptedReleases": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, - "packetDecodeGcKb": 32921, "exactBufferPoolBytes": 0, "adapterGcKb": 0, "exactBufferPoolSizeClasses": 0, - "unreliableCommitBytes": 0, + "reliableCommitBytes": 0, + "commitGcKb": 0, + "receiveDecodeGcSamples": 120801, + "commitGcSamples": 0, + "listenerCallGcKb": -1588, + "streamGrowthBytes": 0, + "deltaZeroBytes": 0, + "exactBufferPoolMisses": 0, + "groupedDecodeBytes": 3485800, + "groupedPackets": 0, + "listenerDispatchGcSamples": 0, + "deltaDecoded": 600, + "groupedHeaderBytesSaved": 0, + "ungroupedPackets": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolRejectedByTotalByteCap": 0, + "deltaStoredBytes": 3489406, + "packetDecodeGcKb": 32981, "sendGcKb": 0, - "receiveDecodeGcKb": 0 + "unreliableCommitBytes": 0, + "deltaInputBytes": 0, + "streamGrowths": 0 }, - "Recieve": 120000 + "Recieve": 120200 }, - "zap": { + "bytenet": { "Framerate": [ 60, 60, @@ -980,87 +1046,87 @@ 60 ], "Bandwidth": [ - 4.941019058227539, - 3.867702007293701, - 4.970263481140137, - 4.99273681640625, - 5.028336524963379, - 5.028336524963379 - ], - "DrainDuration": 4.21691076199977, + 5.011518478393555, + 3.8895363807678224, + 5.079991340637207, + 5.093486309051514, + 5.112719535827637, + 5.112719535827637 + ], + "DrainDuration": 4.450607208000292, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 4.941019315921764, - 3.86770220901008, - 4.970263740359577, - 4.992737076797766, - 5.028336787211565, - 5.028336787211565 + 5.011518739764611, + 3.8895365872453655, + 5.079991605579401, + 5.0934865746975259, + 5.1127198024767399, + 5.1127198024767399 ] }, - "netray_compile": { + "blink": { "Framerate": [ 60, - 53, + 60, 60, 60, 60, 60 ], "Bandwidth": [ - 4.923576831817627, - 3.547711133956909, - 5.275604724884033, - 5.326401233673096, - 5.326401233673096, - 5.326401233673096 - ], - "DrainDuration": 4.231533036000201, - "Recieve": 103000, - "Sent": 103000, - "Frames": 515, + 5.0979084968566898, + 3.990262985229492, + 5.18206262588501, + 5.211799621582031, + 5.217829704284668, + 5.217829704284668 + ], + "DrainDuration": 4.550321310998697, + "Recieve": 120000, + "Sent": 120000, + "Frames": 600, "NormalizedBandwidth": [ - 4.923577088602169, - 3.962882519319976, - 5.275605000028261, - 5.3264015114665679, - 5.3264015114665679, - 5.3264015114665679 + 5.097908762733336, + 3.990263193337924, + 5.1820628961506369, + 5.211799893398563, + 5.217829976415693, + 5.217829976415693 ] }, - "blink": { + "packet": { "Framerate": [ 60, - 58, 60, - 61, - 61, - 61 + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 4.990184783935547, - 3.9084055423736574, - 5.037812232971191, - 5.047177791595459, - 5.047177791595459, - 5.047177791595459 - ], - "DrainDuration": 4.216966139000306, - "Recieve": 119400, - "Sent": 119400, - "Frames": 597, + 4.852776050567627, + 3.7851903438568117, + 4.913506031036377, + 4.950654029846191, + 4.950654029846191, + 4.950654029846191 + ], + "DrainDuration": 4.42767202899995, + "Recieve": 119800, + "Sent": 119800, + "Frames": 599, "NormalizedBandwidth": [ - 5.01952831862577, - 3.9084057462128909, - 5.026492901175637, - 5.037812495713574, - 5.037812495713574, - 5.037812495713574 + 4.872996202665119, + 3.785190558896038, + 4.913506282719627, + 4.950654288042919, + 4.950654288042919, + 4.950654288042919 ] }, - "quicknet": { + "netray_compile": { "Framerate": [ 60, 60, @@ -1070,119 +1136,119 @@ 60 ], "Bandwidth": [ - 4.178702354431152, - 3.3327412605285646, - 4.336594581604004, - 4.363077163696289, - 4.381412029266357, - 4.381412029266357 - ], - "DrainDuration": 4.0829869429999239, + 4.983153820037842, + 3.6808736324310304, + 5.026411056518555, + 5.047861099243164, + 5.061601638793945, + 5.061601638793945 + ], + "DrainDuration": 4.800345408999419, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 4.178702572367463, - 3.332741434344566, - 4.336594807775036, - 4.3630773912484959, - 4.381412257774802, - 4.381412257774802 + 4.983154079929566, + 3.68087382440355, + 5.0264113186663199, + 5.047861362509636, + 5.061601902777042, + 5.061601902777042 ] } }, "Entities": { "warp": { "Framerate": [ - 60, - 48, + 59, + 56, 60, 60, 60, 60 ], "Bandwidth": [ - 2.642598867416382, - 2.339517116546631, - 5.124763488769531, - 5.360812664031982, - 5.360812664031982, - 5.360812664031982 - ], - "DrainDuration": 4.232482034999975, - "Recieve": 114000, - "Sent": 114000, - "Frames": 570, + 3.0112862586975099, + 2.47853946685791, + 8.625332832336426, + 8.867171287536621, + 8.867171287536621, + 8.867171287536621 + ], + "DrainDuration": 5.133183850000933, + "Recieve": 116800, + "Sent": 116800, + "Frames": 584, "NormalizedBandwidth": [ - 2.6425990052386529, - 2.3989964881740288, - 5.545668572695285, - 6.432646181903561, - 6.432646181903561, - 6.432646181903561 + 3.0623251742616448, + 2.4785395961238168, + 9.241428602691706, + 9.450538421155839, + 9.450538421155839, + 9.450538421155839 ] }, "bridgenet2": { "Framerate": [ - 20, - 18, - 21, - 21, - 21, - 21 + 16, + 16, + 16, + 17, + 17, + 17 ], "Bandwidth": [ - 31321.0234375, - 16699.271484375, - 33063.125, - 34682.3515625, - 34682.3515625, - 34682.3515625 - ], - "DrainDuration": 26.12603970100008, - "Recieve": 37400, - "Sent": 37400, - "Frames": 187, + 26509.193359375, + 12183.2041015625, + 26659.837890625, + 26660.3125, + 26660.3125, + 26660.3125 + ], + "DrainDuration": 26.68249219000063, + "Recieve": 30600, + "Sent": 30600, + "Frames": 153, "NormalizedBandwidth": [ - 100015.95861837034, - 48507.410530431356, - 102438.30999190055, - 102808.40569773017, - 102808.40569773017, - 102808.40569773017 + 103551.54001646402, + 44791.1936055797, + 104556.55555094689, + 104835.52060838178, + 104835.52060838178, + 104835.52060838178 ] }, - "packet": { + "quicknet": { "Framerate": [ - 54, - 48, - 56, - 56, - 56, - 56 + 60, + 60, + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 34.13130569458008, - 24.773115158081056, - 36.219722747802737, - 36.26628875732422, - 36.26628875732422, - 36.26628875732422 - ], - "DrainDuration": 7.7387636429998569, - "Recieve": 106200, - "Sent": 106200, - "Frames": 531, + 38.946285247802737, + 30.36568832397461, + 39.02191162109375, + 39.02732849121094, + 39.04610824584961, + 39.04610824584961 + ], + "DrainDuration": 8.316690428000584, + "Recieve": 120200, + "Sent": 120200, + "Frames": 601, "NormalizedBandwidth": [ - 38.80684753601841, - 27.755065370438314, - 39.54637022131169, - 39.65759281404712, - 39.65759281404712, - 39.65759281404712 + 38.946287279009798, + 30.365689907668668, + 39.02191365624503, + 39.02733052664473, + 39.046110282262848, + 39.046110282262848 ] }, - "bytenet": { + "zap": { "Framerate": [ 60, 60, @@ -1192,149 +1258,171 @@ 60 ], "Bandwidth": [ - 38.88546371459961, - 28.805343627929689, - 38.90737533569336, - 38.9367790222168, - 38.95856475830078, - 38.95856475830078 - ], - "DrainDuration": 8.049676222000017, + 39.05140686035156, + 29.573036193847658, + 39.1153678894043, + 39.13433837890625, + 39.16478729248047, + 39.16478729248047 + ], + "DrainDuration": 8.039166375001514, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 38.88546574263458, - 28.805345130245429, - 38.90737736487111, - 38.93678105292807, - 38.95856679014827, - 38.95856679014827 + 39.05140889704114, + 29.573037791285758, + 39.115369929429707, + 39.134340419921048, + 39.1647893350833, + 39.1647893350833 ] }, "roblox": { "Framerate": [ 19, - 17, + 19, 19, 20, 20, 20 ], "Bandwidth": [ - 151953.0625, - 86996.3515625, - 155034.625, - 159276.9375, - 159276.9375, - 159276.9375 - ], - "DrainDuration": 13.747457960000247, - "Recieve": 35800, - "Sent": 35800, - "Frames": 179, + 151282.640625, + 131883.0625, + 166475.609375, + 170386.484375, + 170386.484375, + 170386.484375 + ], + "DrainDuration": 14.448872568998923, + "Recieve": 37400, + "Sent": 37400, + "Frames": 187, "NormalizedBandwidth": [ - 487420.6000677301, - 298445.83610686365, - 547477.9790702512, - 569180.8496718319, - 569180.8496718319, - 569180.8496718319 + 483706.3765852949, + 423414.0764820375, + 526068.3105598508, + 534474.3673987708, + 534474.3673987708, + 534474.3673987708 ] }, "satset": { - "DrainDuration": 7.765782782000315, + "DrainDuration": 3.768260362001456, "DebugStats": { - "reliableCommitBytes": 72480198, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 601, "unreliableCommits": 0, "receiveDecodeGcKb": 0, - "commitGcSamples": 599, - "reliableCommits": 599, - "packetDecodeGcKb": 0, + "groupedRuns": 601, + "exactBufferPoolRejectedBySizeCap": 0, + "reliableCommits": 601, + "deltaSkipped": 1, "listenerCallGcSamples": 0, "packetDecodeGcSamples": 0, "exactBufferPoolRejectedBySizeClassCap": 0, "streamGrowths": 0, - "streamGrowthBytes": 0, + "exactBufferPoolHits": 601, + "adapterGcSamples": 120200, + "sendGcSamples": 240400, + "listenerDispatchGcKb": 0, + "exactBufferPoolBytes": 151618, + "adapterGcKb": 8609, "exactBufferPoolSizeClasses": 3, - "exactBufferPoolHits": 599, - "adapterGcSamples": 119800, - "sendGcSamples": 239600, + "reliableCommitBytes": 72364006, + "commitGcKb": 0, + "receiveDecodeGcSamples": 0, + "commitGcSamples": 601, + "packetDecodeGcKb": 0, + "streamGrowthBytes": 0, + "groupedPackets": 120200, + "exactBufferPoolMisses": 0, + "deltaZeroBytes": 72242400, "listenerDispatchGcSamples": 0, - "listenerDispatchGcKb": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolAcceptedReleases": 599, - "exactBufferPoolRejectedBySizeCap": 0, + "deltaEncoded": 600, + "deltaDecoded": 0, + "groupedHeaderBytesSaved": 358196, + "groupedDecodeBytes": 0, "exactBufferPoolRejectedByPerSizeCap": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolBytes": 153406, - "adapterGcKb": 9366, + "ungroupedPackets": 0, + "deltaStoredBytes": 72364006, + "exactBufferPoolRejectedByTotalByteCap": 0, "sendGcKb": 1, "unreliableCommitBytes": 0, - "listenerCallGcKb": 0, - "receiveDecodeGcSamples": 0 + "deltaInputBytes": 72364006, + "listenerCallGcKb": 0 }, - "Sent": 119800, - "Frames": 599, + "Sent": 120200, + "Frames": 601, "NormalizedBandwidth": [ - 38.95809650589702, - 30.072644848439866, - 39.14834035961604, - 39.23565568109752, - 39.23565568109752, - 39.23565568109752 + 2.6435576863952194, + 2.1237965737773125, + 2.7062441329934296, + 2.714003227636379, + 2.752506161239312, + 2.752506161239312 ], "Framerate": [ 60, - 59, - 61, - 61, - 61, - 61 + 60, + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 38.89755630493164, - 30.072643280029298, - 39.11841583251953, - 39.148338317871097, - 39.148338317871097, - 39.148338317871097 + 2.643557548522949, + 2.1237964630126955, + 2.7062439918518068, + 2.714003086090088, + 2.7525060176849367, + 2.7525060176849367 ], "ServerDebugStats": { - "reliableCommitBytes": 0, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 0, "unreliableCommits": 0, - "listenerDispatchGcSamples": 0, + "receiveDecodeGcKb": 0, + "groupedRuns": 0, "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 0, - "listenerCallGcKb": -3322, - "listenerCallGcSamples": 119800, - "packetDecodeGcSamples": 119800, + "deltaSkipped": 1, + "listenerCallGcSamples": 120200, + "packetDecodeGcSamples": 120200, "exactBufferPoolRejectedBySizeClassCap": 0, - "streamGrowths": 0, - "streamGrowthBytes": 0, - "receiveDecodeGcSamples": 120399, + "deltaEncoded": 0, "exactBufferPoolHits": 0, "adapterGcSamples": 0, "sendGcSamples": 0, - "commitGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolAcceptedReleases": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, - "packetDecodeGcKb": -49204, "exactBufferPoolBytes": 0, "adapterGcKb": 0, "exactBufferPoolSizeClasses": 0, - "unreliableCommitBytes": 0, + "reliableCommitBytes": 0, + "commitGcKb": 0, + "receiveDecodeGcSamples": 120801, + "commitGcSamples": 0, + "listenerCallGcKb": -3822, + "streamGrowthBytes": 0, + "deltaZeroBytes": 0, + "exactBufferPoolMisses": 0, + "groupedDecodeBytes": 72360400, + "groupedPackets": 0, + "listenerDispatchGcSamples": 0, + "deltaDecoded": 600, + "groupedHeaderBytesSaved": 0, + "ungroupedPackets": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolRejectedByTotalByteCap": 0, + "deltaStoredBytes": 72364006, + "packetDecodeGcKb": -65273, "sendGcKb": 0, - "receiveDecodeGcKb": 0 + "unreliableCommitBytes": 0, + "deltaInputBytes": 0, + "streamGrowths": 0 }, - "Recieve": 119800 + "Recieve": 120200 }, - "zap": { + "bytenet": { "Framerate": [ 60, 60, @@ -1344,114 +1432,114 @@ 60 ], "Bandwidth": [ - 38.90581512451172, - 29.449384689331056, - 39.00778579711914, - 39.00813293457031, - 39.04766845703125, - 39.04766845703125 - ], - "DrainDuration": 7.784289073999844, + 38.83589553833008, + 28.443418502807618, + 38.88443374633789, + 38.915061950683597, + 38.962642669677737, + 38.962642669677737 + ], + "DrainDuration": 9.333419555001456, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 38.9058171536081, - 29.449386225236155, - 39.007787831533708, - 39.00813496900298, - 39.04767049352586, - 39.04767049352586 + 38.83589756377987, + 28.443420039227488, + 38.884435774319147, + 38.91506398026223, + 38.9626447017379, + 38.9626447017379 ] }, - "netray_compile": { + "blink": { "Framerate": [ 60, 60, 60, - 61, - 61, - 61 + 60, + 60, + 60 ], "Bandwidth": [ - 38.942970275878909, - 29.502450942993165, - 39.021278381347659, - 39.064598083496097, - 39.064598083496097, - 39.064598083496097 - ], - "DrainDuration": 7.8671460139999, + 38.950984954833987, + 29.00942611694336, + 39.03078842163086, + 39.039920806884769, + 39.10309982299805, + 39.10309982299805 + ], + "DrainDuration": 8.400595144001273, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 38.94297230691308, - 29.38154080468019, - 39.02128041646591, - 39.06460012087365, - 39.06460012087365, - 39.06460012087365 + 38.95098698628615, + 29.00942762990283, + 39.030790457245107, + 39.0399228429753, + 39.103101862383628, + 39.103101862383628 ] }, - "blink": { + "packet": { "Framerate": [ - 60, - 58, - 61, - 61, - 61, - 61 + 57, + 56, + 57, + 59, + 59, + 59 ], "Bandwidth": [ - 38.90694046020508, - 29.5908145904541, - 39.01515579223633, - 39.08964538574219, - 39.08964538574219, - 39.08964538574219 - ], - "DrainDuration": 7.867791304999628, - "Recieve": 118800, - "Sent": 118800, - "Frames": 594, + 36.293724060058597, + 28.05127716064453, + 36.4102668762207, + 36.557830810546878, + 36.557830810546878, + 36.557830810546878 + ], + "DrainDuration": 8.3650661449974, + "Recieve": 112800, + "Sent": 112800, + "Frames": 564, "NormalizedBandwidth": [ - 38.985967762036327, - 29.469542310675086, - 39.08964742442606, - 40.00941117887839, - 40.00941117887839, - 40.00941117887839 + 38.62768965408121, + 28.526723970898034, + 38.886134659836958, + 38.931494137136727, + 38.931494137136727, + 38.931494137136727 ] }, - "quicknet": { + "netray_compile": { "Framerate": [ 60, - 54, + 60, 60, 60, 60, 60 ], "Bandwidth": [ - 38.56299591064453, - 24.317609786987306, - 39.07694625854492, - 39.110050201416019, - 39.110050201416019, - 39.110050201416019 - ], - "DrainDuration": 7.766553356999793, - "Recieve": 110400, - "Sent": 110400, - "Frames": 552, + 39.010597229003909, + 28.90712547302246, + 39.08042907714844, + 39.10653305053711, + 39.20512771606445, + 39.20512771606445 + ], + "DrainDuration": 8.035875301000488, + "Recieve": 120000, + "Sent": 120000, + "Frames": 600, "NormalizedBandwidth": [ - 38.562997921861498, - 27.019567839166095, - 39.076948296566488, - 39.110052241164087, - 39.110052241164087, - 39.110052241164087 + 39.0105992635651, + 28.907127034490256, + 39.080431115351647, + 39.106535090101747, + 39.2051297607712, + 39.2051297607712 ] } }, @@ -1466,87 +1554,87 @@ 60 ], "Bandwidth": [ - 2.2876994609832765, - 1.7765506505966187, - 2.343151569366455, - 2.3724896907806398, - 2.390052318572998, - 2.390052318572998 - ], - "DrainDuration": 3.69888145799996, + 2.1963634490966799, + 1.550009846687317, + 2.2412657737731935, + 2.245737314224243, + 2.285435199737549, + 2.285435199737549 + ], + "DrainDuration": 3.7182652579977004, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 2.287699580296101, - 1.7765507432509554, - 2.3431516915713326, - 2.3724898145156194, - 2.39005244322394, - 2.39005244322394 + 2.19636356364596, + 1.55000992752663, + 2.2412658906643125, + 2.245737431348571, + 2.285435318932283, + 2.285435318932283 ] }, "bridgenet2": { "Framerate": [ + 61, 60, - 60, - 60, - 60, - 60, - 60 + 61, + 61, + 61, + 61 ], "Bandwidth": [ - 145.18408203125, - 114.86489868164063, - 145.40121459960938, - 145.43458557128907, - 145.45436096191407, - 145.45436096191407 - ], - "DrainDuration": 10.400244908999867, + 145.101318359375, + 116.19176483154297, + 145.30340576171876, + 145.53948974609376, + 145.53948974609376, + 145.53948974609376 + ], + "DrainDuration": 11.34825430699857, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 145.18408960318994, - 114.86490467231192, - 145.40122218287366, - 145.43459315629378, - 145.45436854795015, - 145.45436854795015 + 144.9430240534907, + 116.19177089141573, + 145.3023910009614, + 145.3034133398819, + 145.3034133398819, + 145.3034133398819 ] }, - "packet": { + "quicknet": { "Framerate": [ 60, - 58, 60, 60, 60, - 60 + 61, + 61 ], "Bandwidth": [ - 2.081904888153076, - 1.871042251586914, - 2.1085805892944338, - 2.1959121227264406, - 2.3312995433807375, - 2.3312995433807375 - ], - "DrainDuration": 4.185431512999912, - "Recieve": 119600, - "Sent": 119600, - "Frames": 598, + 2.4189276695251467, + 1.9451804161071778, + 2.441330909729004, + 2.4639148712158205, + 2.4743776321411135, + 2.4743776321411135 + ], + "DrainDuration": 3.683399483001267, + "Recieve": 120200, + "Sent": 120200, + "Frames": 601, "NormalizedBandwidth": [ - 2.104463925445428, - 1.8710423491693718, - 2.177805133314769, - 2.195912237252182, - 2.3312996649674835, - 2.3312996649674835 + 2.426267842429615, + 1.945180517556242, + 2.441331037054333, + 2.4639149997189948, + 2.474377761189963, + 2.474377761189963 ] }, - "bytenet": { + "zap": { "Framerate": [ 60, 60, @@ -1556,24 +1644,24 @@ 60 ], "Bandwidth": [ - 2.4102277755737306, - 1.9082095623016358, - 2.449385166168213, - 2.477407455444336, - 2.477407455444336, - 2.477407455444336 - ], - "DrainDuration": 3.4316659649998657, + 2.449782609939575, + 1.9330347776412964, + 2.5079259872436525, + 2.5098865032196047, + 2.533623456954956, + 2.533623456954956 + ], + "DrainDuration": 3.717324003999238, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 2.4102279012769047, - 1.9082096618225198, - 2.4595910648309937, - 2.4774075846512035, - 2.4774075846512035, - 2.4774075846512035 + 2.4497827377056948, + 1.9330348784569163, + 2.5079261180421854, + 2.5098866341203864, + 2.5336235890937166, + 2.5336235890937166 ] }, "roblox": { @@ -1586,149 +1674,171 @@ 60 ], "Bandwidth": [ - 227.17901611328126, - 224.49551391601563, - 228.17010498046876, - 228.95358276367188, - 229.26002502441407, - 229.26002502441407 - ], - "DrainDuration": 9.133543672000087, + 227.37940979003907, + 223.86566162109376, + 227.91363525390626, + 228.91610717773438, + 229.07762145996095, + 229.07762145996095 + ], + "DrainDuration": 9.866552233997937, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 227.17902796159025, - 224.49552562436907, - 228.17011688046706, - 228.95359470453173, - 229.26003698125613, - 229.26003698125613 + 227.3794216487994, + 223.86567329659784, + 227.9136471405286, + 228.91611911663973, + 229.0776334072899, + 229.0776334072899 ] }, "satset": { - "DrainDuration": 3.4324183919998179, + "DrainDuration": 3.71662052299871, "DebugStats": { - "reliableCommitBytes": 241602, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 601, "unreliableCommits": 0, "receiveDecodeGcKb": 0, - "commitGcSamples": 601, + "groupedRuns": 601, + "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 601, - "packetDecodeGcKb": 0, + "deltaSkipped": 1, "listenerCallGcSamples": 0, "packetDecodeGcSamples": 0, "exactBufferPoolRejectedBySizeClassCap": 0, "streamGrowths": 0, - "streamGrowthBytes": 0, - "exactBufferPoolSizeClasses": 5, "exactBufferPoolHits": 600, "adapterGcSamples": 120200, "sendGcSamples": 240400, - "listenerDispatchGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolAcceptedReleases": 601, - "exactBufferPoolRejectedBySizeCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolBytes": 585030, + "adapterGcKb": 9387, + "exactBufferPoolSizeClasses": 5, + "reliableCommitBytes": 123806, + "commitGcKb": 1, + "receiveDecodeGcSamples": 0, + "commitGcSamples": 601, + "packetDecodeGcKb": 0, + "streamGrowthBytes": 0, + "groupedPackets": 120200, "exactBufferPoolMisses": 1, - "exactBufferPoolBytes": 587610, - "adapterGcKb": 9389, + "deltaZeroBytes": 122400, + "listenerDispatchGcSamples": 0, + "deltaEncoded": 600, + "deltaDecoded": 0, + "groupedHeaderBytesSaved": 117796, + "groupedDecodeBytes": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "ungroupedPackets": 0, + "deltaStoredBytes": 123806, + "exactBufferPoolRejectedByTotalByteCap": 0, "sendGcKb": 1, "unreliableCommitBytes": 0, - "listenerCallGcKb": 0, - "receiveDecodeGcSamples": 0 + "deltaInputBytes": 123806, + "listenerCallGcKb": 0 }, "Sent": 120200, "Frames": 601, "NormalizedBandwidth": [ - 2.4630578848824227, - 1.9384501991357706, - 2.519162071005505, - 2.554712667189447, - 2.5740772636125976, - 2.5740772636125976 + 2.2791274782560135, + 1.8172593167005018, + 2.334997228282054, + 2.3357912465903207, + 2.3357912465903207, + 2.3357912465903207 ], "Framerate": [ 60, 60, - 60, - 60, - 60, - 60 + 61, + 61, + 61, + 61 ], "Bandwidth": [ - 2.46305775642395, - 1.9384500980377198, - 2.5191619396209719, - 2.5547125339508058, - 2.5740771293640138, - 2.5740771293640138 + 2.282984733581543, + 1.8097187280654908, + 2.325308322906494, + 2.3454034328460695, + 2.3454034328460695, + 2.3454034328460695 ], "ServerDebugStats": { - "reliableCommitBytes": 0, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 0, "unreliableCommits": 0, - "listenerDispatchGcSamples": 0, + "receiveDecodeGcKb": 1, + "groupedRuns": 0, "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 0, - "listenerCallGcKb": -1874, + "deltaSkipped": 1, "listenerCallGcSamples": 120200, "packetDecodeGcSamples": 120200, "exactBufferPoolRejectedBySizeClassCap": 0, - "streamGrowths": 0, - "streamGrowthBytes": 0, - "receiveDecodeGcSamples": 120801, + "deltaEncoded": 0, "exactBufferPoolHits": 0, "adapterGcSamples": 0, "sendGcSamples": 0, - "commitGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolAcceptedReleases": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, - "packetDecodeGcKb": 6757, "exactBufferPoolBytes": 0, "adapterGcKb": 0, "exactBufferPoolSizeClasses": 0, - "unreliableCommitBytes": 0, + "reliableCommitBytes": 0, + "commitGcKb": 0, + "receiveDecodeGcSamples": 120801, + "commitGcSamples": 0, + "listenerCallGcKb": -1995, + "streamGrowthBytes": 0, + "deltaZeroBytes": 0, + "exactBufferPoolMisses": 0, + "groupedDecodeBytes": 120200, + "groupedPackets": 0, + "listenerDispatchGcSamples": 0, + "deltaDecoded": 600, + "groupedHeaderBytesSaved": 0, + "ungroupedPackets": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolRejectedByTotalByteCap": 0, + "deltaStoredBytes": 123806, + "packetDecodeGcKb": 6493, "sendGcKb": 0, - "receiveDecodeGcKb": 0 + "unreliableCommitBytes": 0, + "deltaInputBytes": 0, + "streamGrowths": 0 }, "Recieve": 120200 }, - "zap": { + "bytenet": { "Framerate": [ 60, 60, - 61, - 61, - 61, - 61 + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 2.338430881500244, - 1.8679817914962769, - 2.369088888168335, - 2.3784589767456056, - 2.3784589767456056, - 2.3784589767456056 - ], - "DrainDuration": 3.716088968999884, + 2.4088504314422609, + 1.913741111755371, + 2.4710817337036135, + 2.5060698986053469, + 2.529073715209961, + 2.529073715209961 + ], + "DrainDuration": 3.7160617980007375, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 2.3399690973322215, - 1.867981888919119, - 2.378015016253504, - 2.3784591007919078, - 2.3784591007919078, - 2.3784591007919078 + 2.4088505570736009, + 1.9137412133470582, + 2.4710818625805688, + 2.506070029307077, + 2.529073847111434, + 2.529073847111434 ] }, - "netray_compile": { + "blink": { "Framerate": [ 60, 60, @@ -1738,27 +1848,27 @@ 60 ], "Bandwidth": [ - 2.4127581119537355, - 1.915351390838623, - 2.4495925903320314, - 2.4844465255737306, - 2.4844465255737306, - 2.4844465255737306 - ], - "DrainDuration": 3.8176179279998907, + 2.431198835372925, + 1.9331190586090088, + 2.4876718521118166, + 2.5157177448272707, + 2.5231070518493654, + 2.5231070518493654 + ], + "DrainDuration": 3.7172953209992558, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 2.4127582377888769, - 1.9153514907319825, - 2.4495927180882406, - 2.484446655147714, - 2.484446655147714, - 2.484446655147714 + 2.431198962169825, + 1.9331191612293817, + 2.487671981854014, + 2.5157178760321754, + 2.5231071834396525, + 2.5231071834396525 ] }, - "blink": { + "packet": { "Framerate": [ 60, 60, @@ -1768,54 +1878,54 @@ 60 ], "Bandwidth": [ - 2.3603014945983888, - 1.6909959316253663, - 2.4237380027770998, - 2.440016508102417, - 2.458296298980713, - 2.458296298980713 - ], - "DrainDuration": 3.699903177000124, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 2.3341825008392336, + 1.8568410873413087, + 2.3711836338043215, + 2.396146774291992, + 2.396146774291992, + 2.396146774291992 + ], + "DrainDuration": 3.7411593480028388, + "Recieve": 119800, + "Sent": 119800, + "Frames": 599, "NormalizedBandwidth": [ - 2.3603016176977045, - 1.6980418357794856, - 2.423738129184887, - 2.4400166353591947, - 2.458296427190856, - 2.458296427190856 + 2.3341826225763375, + 1.8568411841831179, + 2.3719501542376499, + 2.396146899260785, + 2.396146899260785, + 2.396146899260785 ] }, - "quicknet": { + "netray_compile": { "Framerate": [ 60, 60, - 61, - 61, - 61, - 61 + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 2.3922231197357179, - 1.9022480249404908, - 2.4450292587280275, - 2.4501376152038576, - 2.4501376152038576, - 2.4501376152038576 - ], - "DrainDuration": 3.748861912999928, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 2.422775983810425, + 1.85535728931427, + 2.4773504734039308, + 2.4954605102539064, + 2.4954605102539064, + 2.4954605102539064 + ], + "DrainDuration": 3.7153819179984568, + "Recieve": 119800, + "Sent": 119800, + "Frames": 599, "NormalizedBandwidth": [ - 2.392223244499876, - 1.9022481241504564, - 2.4400961959837206, - 2.44502938624624, - 2.44502938624624, - 2.44502938624624 + 2.422776110168039, + 1.8553573860786932, + 2.4773506026078264, + 2.495460640402314, + 2.495460640402314, + 2.495460640402314 ] } }, @@ -1825,29 +1935,29 @@ 60, 60, 60, - 60, - 60, - 60 + 61, + 61, + 61 ], "Bandwidth": [ - 3.448098659515381, - 3.3145055770874025, - 6.075814247131348, - 6.445077419281006, - 15.775246620178223, - 15.775246620178223 - ], - "DrainDuration": 3.7676475160001248, + 15.87022590637207, + 9.377838134765625, + 19.789161682128908, + 30.28354835510254, + 30.28354835510254, + 30.28354835510254 + ], + "DrainDuration": 6.249631500999385, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 3.4480988393477398, - 3.314505749952339, - 6.075814564009754, - 6.445077755417987, - 15.775247442921448, - 15.775247442921448 + 15.87022673406885, + 9.377838623857997, + 19.789162714214116, + 30.15943703881569, + 30.15943703881569, + 30.15943703881569 ] }, "bridgenet2": { @@ -1860,326 +1970,348 @@ 60 ], "Bandwidth": [ - 27188.83984375, - 21572.578125, - 27291.423828125, - 27292.138671875, - 27306.3828125, - 27306.3828125 - ], - "DrainDuration": 39.9191793670002, + 27248.35546875, + 20858.775390625, + 27253.99609375, + 27270.904296875, + 27280.87890625, + 27280.87890625 + ], + "DrainDuration": 24.100667700000487, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 27188.841261758498, - 21572.579250097624, - 27291.42525148367, - 27292.140095270952, - 27306.38423663884, - 27306.38423663884 + 27248.35688986248, + 20858.77647849491, + 27253.99751515666, + 27270.905719163493, + 27280.88032905871, + 27280.88032905871 ] }, - "packet": { + "quicknet": { "Framerate": [ 60, 60, - 61, - 61, + 60, + 60, 61, 61 ], "Bandwidth": [ - 95.8426284790039, - 73.63484191894531, - 96.41002655029297, - 96.52901458740235, - 96.52901458740235, - 96.52901458740235 - ], - "DrainDuration": 9.449241188000088, + 96.48114776611328, + 76.49605560302735, + 96.67337036132813, + 96.73574829101563, + 96.91983795166016, + 96.91983795166016 + ], + "DrainDuration": 10.249803613998666, "Recieve": 120200, "Sent": 120200, "Frames": 601, "NormalizedBandwidth": [ - 95.8426334775865, - 73.63484575930159, - 96.41003157846768, - 96.52901962178277, - 96.52901962178277, - 96.52901962178277 + 96.44951369722605, + 76.49605959260754, + 96.73575333617805, + 96.9198430064236, + 96.95226909790046, + 96.95226909790046 ] }, - "bytenet": { + "zap": { "Framerate": [ - 0, - 0, - 0, - 0, - 0, - 0 + 60, + 57, + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "DrainDuration": 3.0139235769997869, - "Recieve": 1400, - "Sent": 1400, - "Frames": 7, + 100.29531860351563, + 75.00415802001953, + 100.64071655273438, + 100.75215911865235, + 100.7898178100586, + 100.7898178100586 + ], + "DrainDuration": 10.315592274000665, + "Recieve": 119400, + "Sent": 119400, + "Frames": 597, "NormalizedBandwidth": [ - 0, - 0, - 0, - 0, - 0, - 0 + 100.29532383432411, + 75.0041619317912, + 100.64072180155677, + 100.75216437328692, + 100.78982306665722, + 100.78982306665722 ] }, "roblox": { "Framerate": [ 60, + 59, 60, - 60, - 60, - 60, - 60 + 61, + 61, + 61 ], "Bandwidth": [ - 80407.65625, - 70049.5078125, - 83918.40625, - 86964.1328125, - 92304.15625, - 92304.15625 - ], - "DrainDuration": 10.867273030999968, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 94127.4921875, + 80212.203125, + 113477.78125, + 121457.6796875, + 121457.6796875, + 121457.6796875 + ], + "DrainDuration": 11.334043584000029, + "Recieve": 119800, + "Sent": 119800, + "Frames": 599, "NormalizedBandwidth": [ - 80407.66044358607, - 70049.51146586653, - 83918.41062668595, - 86964.13734803297, - 92304.16106403689, - 92304.16106403689 + 94127.49709663127, + 80212.20730839239, + 113477.78716832749, + 121457.68602201162, + 121457.68602201162, + 121457.68602201162 ] }, "satset": { - "DrainDuration": 9.74898279800027, + "DrainDuration": 4.184178742998483, "DebugStats": { - "reliableCommitBytes": 260715002, - "commitGcKb": 424, + "exactBufferPoolAcceptedReleases": 601, "unreliableCommits": 0, "receiveDecodeGcKb": 0, - "commitGcSamples": 601, + "groupedRuns": 601, + "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 601, - "packetDecodeGcKb": 0, + "deltaSkipped": 1, "listenerCallGcSamples": 0, "packetDecodeGcSamples": 0, "exactBufferPoolRejectedBySizeClassCap": 0, "streamGrowths": 2, - "streamGrowthBytes": 786432, - "exactBufferPoolSizeClasses": 4, "exactBufferPoolHits": 600, "adapterGcSamples": 120200, "sendGcSamples": 240402, - "listenerDispatchGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolAcceptedReleases": 601, - "exactBufferPoolRejectedBySizeCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolBytes": 584824, + "adapterGcKb": 8470, + "exactBufferPoolSizeClasses": 4, + "reliableCommitBytes": 260356806, + "commitGcKb": 423, + "receiveDecodeGcSamples": 0, + "commitGcSamples": 601, + "packetDecodeGcKb": 0, + "streamGrowthBytes": 786432, + "groupedPackets": 120200, "exactBufferPoolMisses": 1, - "exactBufferPoolBytes": 587208, - "adapterGcKb": 9308, - "sendGcKb": 770, + "deltaZeroBytes": 259922400, + "listenerDispatchGcSamples": 0, + "deltaEncoded": 600, + "deltaDecoded": 0, + "groupedHeaderBytesSaved": 358196, + "groupedDecodeBytes": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "ungroupedPackets": 0, + "deltaStoredBytes": 260356806, + "exactBufferPoolRejectedByTotalByteCap": 0, + "sendGcKb": 769, "unreliableCommitBytes": 0, - "listenerCallGcKb": 0, - "receiveDecodeGcSamples": 0 + "deltaInputBytes": 260356806, + "listenerCallGcKb": 0 }, "Sent": 120200, "Frames": 601, "NormalizedBandwidth": [ - 96.4289829954717, - 73.35116196765086, - 96.59933975484478, - 96.7008564837721, - 96.84271745308479, - 96.84271745308479 + 3.282013825879213, + 2.712485022058923, + 3.3114406405395515, + 3.326693454482635, + 3.326693454482635, + 3.326693454482635 ], "Framerate": [ 60, 60, - 60, - 60, - 60, - 60 + 61, + 61, + 61, + 61 ], "Bandwidth": [ - 96.4289779663086, - 73.35115814208985, - 96.59933471679688, - 96.70085144042969, - 96.84271240234375, - 96.84271240234375 + 3.2820136547088625, + 2.723647356033325, + 3.3114404678344728, + 3.312889575958252, + 3.312889575958252, + 3.312889575958252 ], "ServerDebugStats": { - "reliableCommitBytes": 0, - "commitGcKb": 0, + "exactBufferPoolAcceptedReleases": 0, "unreliableCommits": 0, - "listenerDispatchGcSamples": 0, + "receiveDecodeGcKb": 0, + "groupedRuns": 0, "exactBufferPoolRejectedBySizeCap": 0, "reliableCommits": 0, - "listenerCallGcKb": -529, + "deltaSkipped": 1, "listenerCallGcSamples": 120200, "packetDecodeGcSamples": 120200, "exactBufferPoolRejectedBySizeClassCap": 0, - "streamGrowths": 0, - "streamGrowthBytes": 0, - "receiveDecodeGcSamples": 120801, + "deltaEncoded": 0, "exactBufferPoolHits": 0, "adapterGcSamples": 0, "sendGcSamples": 0, - "commitGcSamples": 0, "listenerDispatchGcKb": 0, - "exactBufferPoolMisses": 0, - "exactBufferPoolAcceptedReleases": 0, - "exactBufferPoolRejectedByTotalByteCap": 0, - "exactBufferPoolRejectedByPerSizeCap": 0, - "packetDecodeGcKb": -249839, "exactBufferPoolBytes": 0, "adapterGcKb": 0, "exactBufferPoolSizeClasses": 0, - "unreliableCommitBytes": 0, + "reliableCommitBytes": 0, + "commitGcKb": 0, + "receiveDecodeGcSamples": 120801, + "commitGcSamples": 0, + "listenerCallGcKb": -4, + "streamGrowthBytes": 0, + "deltaZeroBytes": 0, + "exactBufferPoolMisses": 0, + "groupedDecodeBytes": 260353200, + "groupedPackets": 0, + "listenerDispatchGcSamples": 0, + "deltaDecoded": 600, + "groupedHeaderBytesSaved": 0, + "ungroupedPackets": 0, + "exactBufferPoolRejectedByPerSizeCap": 0, + "exactBufferPoolRejectedByTotalByteCap": 0, + "deltaStoredBytes": 260356806, + "packetDecodeGcKb": -251713, "sendGcKb": 0, - "receiveDecodeGcKb": 0 + "unreliableCommitBytes": 0, + "deltaInputBytes": 0, + "streamGrowths": 0 }, "Recieve": 120200 }, - "zap": { + "bytenet": { "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 + 0, + 0, + 0, + 0, + 0, + 0 ], "Bandwidth": [ - 100.29021453857422, - 75.2690200805664, - 100.6224594116211, - 100.70121002197266, - 100.77957916259766, - 100.77957916259766 - ], - "DrainDuration": 11.881539900999997, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "DrainDuration": 4.5666492720010869, + "Recieve": 1600, + "Sent": 1600, + "Frames": 8, "NormalizedBandwidth": [ - 100.2902197691165, - 75.26902400615171, - 100.6224646594913, - 100.70121527395003, - 100.7795844186623, - 100.7795844186623 + 0, + 0, + 0, + 0, + 0, + 0 ] }, - "netray_compile": { + "blink": { "Framerate": [ + 61, 60, - 60, - 60, - 60, - 60, - 60 + 61, + 61, + 61, + 61 ], "Bandwidth": [ - 96.3437728881836, - 73.3901138305664, - 96.48297119140625, - 96.84610748291016, - 97.13309478759766, - 97.13309478759766 - ], - "DrainDuration": 9.89921039300043, + 100.5748519897461, + 77.4182357788086, + 100.83402252197266, + 101.02582550048828, + 101.02582550048828, + 101.02582550048828 + ], + "DrainDuration": 10.182840838002449, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 96.34377791290291, - 73.39011765815912, - 96.48297622338532, - 96.84611253382826, - 97.13309985348332, - 97.13309985348332 + 100.56346654850415, + 77.41823988740359, + 101.02583076939567, + 101.25416951634256, + 101.25416951634256, + 101.25416951634256 ] }, - "blink": { + "packet": { "Framerate": [ 60, 60, - 61, + 60, 61, 61, 61 ], "Bandwidth": [ - 100.3739013671875, - 76.27218627929688, - 100.47459411621094, - 100.83391571044922, - 100.83391571044922, - 100.83391571044922 - ], - "DrainDuration": 9.965582208000342, - "Recieve": 120000, - "Sent": 120000, - "Frames": 600, + 95.68421173095703, + 73.59619903564453, + 96.0799789428711, + 96.29296112060547, + 96.29296112060547, + 96.29296112060547 + ], + "DrainDuration": 10.116405787000986, + "Recieve": 119800, + "Sent": 119800, + "Frames": 599, "NormalizedBandwidth": [ - 100.36395787110678, - 76.58999101442271, - 100.79213116619622, - 100.83885672784609, - 100.83885672784609, - 100.83885672784609 + 95.68421672127755, + 73.29457905800591, + 96.07998395383248, + 96.29296614267475, + 96.29296614267475, + 96.29296614267475 ] }, - "quicknet": { + "netray_compile": { "Framerate": [ 60, 60, - 61, - 61, - 61, - 61 + 60, + 60, + 60, + 60 ], "Bandwidth": [ - 96.38121032714844, - 47.9400634765625, - 96.53449249267578, - 96.77434539794922, - 96.77434539794922, - 96.77434539794922 - ], - "DrainDuration": 9.816952269000013, + 96.6142578125, + 72.72855377197266, + 96.72049713134766, + 96.79713439941406, + 97.12109375, + 97.12109375 + ], + "DrainDuration": 10.398333981000178, "Recieve": 120000, "Sent": 120000, "Frames": 600, "NormalizedBandwidth": [ - 96.37773429910109, - 48.13981622941128, - 96.5344975273419, - 96.81548622770203, - 96.81548622770203, - 96.81548622770203 + 96.6142628513262, + 72.72855756506232, + 96.72050217571467, + 96.79713944777802, + 97.12109881525976, + 97.12109881525976 ] } } diff --git a/docs/api/channel.md b/docs/api/channel.md index 70d8300..e3f6de9 100644 --- a/docs/api/channel.md +++ b/docs/api/channel.md @@ -1,6 +1,6 @@ # Channel API -Objects returned by `Satset.defineChannel`. Channels are designed for stateful synchronization with delta compression. +Objects returned by `Satset.defineChannel`. Channels sync fixed-size state through keyframes and dirty-field updates. ## Configuration @@ -14,21 +14,21 @@ type ChannelConfig = { ``` > [!IMPORTANT] -> Channels only support **fixed-size types**. This allows the engine to pre-compute buffer offsets and perform zero-allocation updates. -> There is a limit of **32 fields** per channel. This is a technical constraint of the 32-bit dirty bitmask tracking used to keep delta updates O(1) and compact. +> Channels only support **fixed-size types**. Satset pre-computes field offsets and writes updates into the entity buffer. +> There is a limit of **32 fields** per channel because dirty tracking uses a 32-bit bitmask. ## Methods (Channel Object) ### `:create(entityId: number, initialData: table?): Entity` -**Server Only.** Creates a new stateful entity instance. +**Server Only.** Creates a stateful entity instance. The first flush sends a full keyframe. - **entityId**: A unique identifier for the entity (e.g., `player.UserId` or a GUID). - **initialData**: Optional initial state. ### `:subscribe(callback: (entityId: number, state: table) -> ())` -**Client Only.** Registers a listener for state updates. The callback is triggered whenever any field in the entity changes. Subscribers are wrapped in `xpcall`, so an error in one subscriber will not affect others or halt state synchronization. +**Client Only.** Registers a listener for state updates. Subscribers are wrapped in `xpcall`, so one failing callback does not stop the rest. ## Entity Object @@ -36,7 +36,7 @@ Returned by `:create()`. Represents a single stateful instance on the server. ### `:set(fieldName: string, value: any)` -Updates a field. Only this specific field (the delta) will be transmitted to clients in the next frame. +Updates a field. The next flush sends that field, unless the entity is due for a keyframe. ### `:get(fieldName: string): any` @@ -48,4 +48,4 @@ Returns a dictionary containing the full current state. ### `:destroy()` -Removes the entity and stops synchronization. +Removes the entity and stops syncing it. diff --git a/docs/api/guard.md b/docs/api/guard.md index 43acfec..d87620b 100644 --- a/docs/api/guard.md +++ b/docs/api/guard.md @@ -6,16 +6,18 @@ Configuration for server-side rate limiting. ```lua type GuardConfig = { - maxTokens: number?, -- Max burst capacity (default: 60) - refillRate: number?, -- Tokens refilled per second (default: 30) + maxTokens: number?, -- Max burst capacity (default: 1000) + refillRate: number?, -- Tokens refilled per second (default: 500) onFlood: ((player: Player) -> ())?, -- Optional flood callback + studioBypass: boolean?, -- Skip rate limiting in Studio (default: true) } ``` The Guard uses a **Token Bucket** algorithm. Each player has a bucket that starts full with `maxTokens`. -- Every packet sent consumes **1 token**. -- Tokens refill automatically over time at the speed of `refillRate` per second. +- Every incoming client packet consumes **1 token**. +- Tokens refill at `refillRate` per second. - If a player runs out of tokens, any further packets from them are dropped until the bucket has at least 1 token again. +- In Studio, rate limiting is skipped by default. Published servers always use the bucket. -This allows players to send small bursts of traffic without being dropped, while still enforcing a strict average limit. +The defaults are tuned for the benchmark harness and high-frequency game traffic. Tighten them per game if the server expects lower packet volume. diff --git a/docs/api/packet.md b/docs/api/packet.md index 5b2014f..3b2d6ad 100644 --- a/docs/api/packet.md +++ b/docs/api/packet.md @@ -6,15 +6,15 @@ Objects returned by `Satset.definePacket`. ### `:fireServer(data: table)` -**Client Only.** Sends data to the server. Automatically batched. +**Client Only.** Queues data for the server. Satset sends the batch during `PostSimulation`. ### `:fireClient(player: Player, data: table)` -**Server Only.** Sends data to a specific player. Automatically batched. +**Server Only.** Queues data for a specific player. ### `:fireAllClients(data: table)` -**Server Only.** Sends data to all players. Automatically batched. +**Server Only.** Queues data for all players. ### `:listen(callback: (data: table, sender: Player?) -> ())` diff --git a/docs/api/satset.md b/docs/api/satset.md index b5fd8f9..c344841 100644 --- a/docs/api/satset.md +++ b/docs/api/satset.md @@ -6,12 +6,12 @@ The main entry point for the library. ### `Satset.start(config: SatsetConfig?)` -Initializes the networking engine. Must be called once on both server and client. Calling it more than once is safe; later calls are ignored with a warning. +Starts the networking engine. Call it once on both server and client before defining packets or channels. Later calls are ignored with a warning. - **config**: Optional configuration object. - **guard**: Guard configuration (see [Guard API](./guard.md)). - **batching**: Batching behavior. - - **reliableThreshold**: (number) Size in bytes before a reliable stream is committed and a new stream begins. Default is `60000`. Set to `0` to disable threshold splitting; reliable packets are committed at the frame flush. + - **reliableThreshold**: (number) Size in bytes before a reliable stream is committed and a new stream begins. Default is `0`, which commits reliable traffic at the frame flush. - **unreliableThreshold**: (number) Size in bytes before an unreliable stream is committed and a new stream begins. Default is `900`. - **maxPacketsPerFrame**: (number) Maximum committed batches sent per frame for each queue. Default is `0` (no cap). @@ -56,7 +56,7 @@ Reference to the [Types module](./types.md). ## Performance Context -Initialization and packet definitions are O(1) operations. For benchmark results under heavy local Studio load, see the [Benchmarks Report](../../benchmark/Benchmarks.md). +Startup and packet definitions are O(1) operations. Reliable traffic uses frame batching, run grouping for repeated packet ids, and same-size XOR delta encoding for direct reliable batches. For local Studio numbers, see the [Benchmarks Report](../../benchmark/Benchmarks.md). ## Related Guides diff --git a/docs/api/types.md b/docs/api/types.md index d509454..35891d6 100644 --- a/docs/api/types.md +++ b/docs/api/types.md @@ -1,6 +1,6 @@ # Types API -Standard and optimized types for schemas. +Schema types for packets, channels, and nested structs. ## Primitives @@ -19,7 +19,7 @@ Standard and optimized types for schemas. ## Strings -- `string`: Recommended default. Alias for `string16` (2 + N bytes). +- `string`: ByteNet-style alias for `string16` (2 + N bytes). - `string8`: Max length 255 chars (1 + N bytes). Consists of a 1-byte `u8` length header followed by N bytes of UTF-8 string data. - `string16`: Max length 65,535 chars (2 + N bytes). Consists of a 2-byte `u16` length header followed by N bytes of UTF-8 string data. @@ -27,7 +27,7 @@ Standard and optimized types for schemas. - `Vector3`: Standard [IEEE 754 single-precision floating-point format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) (12 bytes). It writes three 4-byte `f32` values (X, Y, Z). - `Vector2`: Standard [IEEE 754 single-precision floating-point format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) (8 bytes). It writes two 4-byte `f32` values (X, Y). -- `Color3`: Compressed RGB (3 bytes). We extract the R, G, and B color channels and write each as a single 1-byte `u8` integer (0-255). +- `Color3`: Compressed RGB (3 bytes). Satset writes R, G, and B as single-byte values (0-255). - `CFrame`: Compressed using **"smallest-three" quaternion compression** (18 bytes). Standard uncompressed [Roblox CFrames](https://create.roblox.com/docs/reference/engine/datatypes/CFrame) contain a Vector3 position and a 9-element rotation matrix. Satset compresses the rotation into a quaternion, omits the largest component, and transmits only the 3 smallest components alongside the position. ## Collections @@ -35,18 +35,18 @@ Standard and optimized types for schemas. - `optional(type)`: Nullable type. Adds a **1-byte header** (a `u8` boolean flag) to check for `nil`. - `array(type)`: Array of a specific type. Includes a **2-byte header** (a `u16` integer) for length (max 65,535 elements) followed by the array elements. - `map(keyType, valType)`: Dictionary mapping. Includes a **2-byte header** (a `u16` integer) for element count. Keys are sorted alphabetically during serialization to produce deterministic byte order output. -- `enum(values: {string})`: Efficient mapping. Converts string constants into a **single 1-byte `u8` index** corresponding to its array position. +- `enum(values: {string})`: Maps string constants to a **single 1-byte `u8` index** based on array position. ## Specialized -- `Vector3Quantized(range: number)`: Compressed Vector3 (6 bytes). Maps a stud range (default 1024) into 16-bit signed integers. Provides ~0.03 stud precision while reducing size by 50% compared to standard f32. +- `Vector3Quantized(range: number)`: Compressed Vector3 (6 bytes). Maps a stud range (default 1024) into 16-bit signed integers. Provides about 0.03 stud precision and halves the size of standard f32 vectors. - `Vector2Quantized(range: number)`: Compressed Vector2 (4 bytes). Same quantization approach for 2D coordinates. -## Readable Aliases +## Aliases -Satset provides human-readable aliases for all primitive types. These are the recommended names for cleaner, more self-documenting schemas. The shorthand forms (`u8`, `f64`, etc.) remain available for developers who prefer brevity. +Satset keeps ByteNet-style names for compatibility and migration. The shorthand forms (`u8`, `f64`, etc.) remain the native names. -| Recommended | Shorthand | +| Alias | Native | | :--- | :--- | | `string` | `string16` | | `uint8` | `u8` | diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index 7c6b07e..662dc17 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -1,6 +1,6 @@ # Architecture -This document describes the internal data flow of Satset. It covers how packets and channels move from the developer's API call through serialization, batching, and transport, and how incoming data is validated and dispatched on the receiving end. +This document describes Satset's internal data flow. It follows packets and channels from the public API call through serialization, batching, transport, validation, and dispatch. For a high-level overview, see the [data flow diagram in the README](../../README.md#architecture). @@ -17,7 +17,7 @@ Satset is organized into four layers: ## Packet Lifecycle (Stateless) -The following diagram traces a single `fireServer()` call from the client to the server, including the batching and validation steps. +The following diagram traces a single reliable `fireServer()` call from the client to the server. ```mermaid sequenceDiagram @@ -41,16 +41,17 @@ sequenceDiagram Note over BT: PostSimulation fires once per frame BT->>BT: commitStream(queue) - opt reliableThreshold > 0 and stream exceeds threshold - BT->>BT: commit current buffer and start a new one - end - Note right of BT: Wire: [u16 count][u8 id, (opt u16 size), payload]... + BT->>BT: compact repeated packet-id runs + BT->>BT: encode same-size reliable delta + Note right of BT: Wire: [u16 count+flags][entry...] or [run marker, id, count, payloads...] BT->>BR: getReliable() BR->>RE: FireServer(batchBuffer) Note over RE: Crosses the network boundary - RE->>GD: OnServerEvent(player, batchBuffer) + RE->>BT: OnServerEvent(player, batchBuffer) + BT->>BT: decode reliable delta + BT->>GD: decoded batchBuffer GD->>GD: consume(player) via token bucket alt Token available GD->>PK: _dispatch(batchBuffer, player) @@ -59,7 +60,7 @@ sequenceDiagram BT->>PK: dispatch(packetId, buffer, payloadOffset, sender) PK->>PK: pcall(decodeToTable) PK->>SR: decodeToTable(compiled, buffer, offset) - SR->>SN: sanitizeFloat(value) for each number field + SR->>SN: sanitizeFloat(value) for floating-point fields SR->>SN: checkBounds(buffer, cursor, size) SR-->>PK: decoded data table PK->>Dev: listener(data, sender) via pcall @@ -72,16 +73,30 @@ sequenceDiagram ### Wire Format (Reliable Batch) +The reliable batch header stores a 14-bit item count plus two flags in the high bits: + +- `0x4000`: the batch is tracked for reliable delta state; +- `0x8000`: the payload after the header is XOR delta data; +- `0x3FFF`: count mask. + ```luau -[u16 packetCount] +[u16 packetCountAndFlags] [u8 packetId][u16 payloadSize][...payload bytes] (Variable size) [u8 packetId][...payload bytes] (Fixed size - size header omitted) ... ``` +When a reliable batch contains repeated entries with the same packet id, Satset can compact that run: + +```luau +[u8 0][u8 packetId][u16 runCount][payload][payload]... +``` + +The run marker is `0`, which is reserved internally. Fixed-size payloads can be split by the compiled packet size. Variable-size payloads are decoded by the packet serializer, which returns the consumed byte count to the batcher. + ### Wire Format (Unreliable Batch) -Unreliable batches include a sequence number for stale packet detection. If the batch exceeds 900 bytes (MTU limit), it is automatically split into multiple sub-batches, each with its own sequence number. +Unreliable batches include a sequence number for stale packet detection. If the batch exceeds 900 bytes, Satset commits the current stream and starts a new sub-batch with its own sequence number. ```luau [u16 sequenceNumber][u16 packetCount] @@ -92,7 +107,7 @@ Unreliable batches include a sequence number for stale packet detection. If the ## Channel Lifecycle (Stateful) -Channels handle delta-compressed state synchronization. Instead of sending full state every frame, they track which fields have changed using a 32-bit bitmask and only transmit the dirty bytes. +Channels handle state sync for fixed-size schemas. A channel writes entity state into a buffer, marks changed fields in a 32-bit bitmask, and sends the dirty bytes during `PostSimulation`. ```mermaid sequenceDiagram @@ -144,48 +159,35 @@ sequenceDiagram [u8 channelId][u32 entityId][u32 dirtyMask][...dirty field bytes] ``` -When `dirtyMask == 0xFFFFFFFF` (all bits set), the payload contains the full state buffer. This is used for initial synchronization and periodic resyncs (controlled by `resyncInterval`). +When `dirtyMask == 0xFFFFFFFF` (all bits set), the payload contains the full state buffer. This is used for the first sync and periodic resyncs (controlled by `resyncInterval`). ### Resync Mechanism -Channels periodically send a full keyframe to prevent client-side state drift caused by dropped unreliable packets. The default interval is 5 seconds, configurable via `resyncInterval` in the channel definition. During a resync frame, all entities in the channel receive a full state transmission regardless of their dirty mask. +Channels periodically send a full keyframe to prevent client-side state drift caused by dropped unreliable packets. The default interval is 5 seconds, configurable via `resyncInterval` in the channel definition. During a resync frame, all entities in the channel receive a full state payload regardless of their dirty mask. ## Validation Pipeline -Satset processes incoming data through a strict validation stack before it reaches the developer's listener: +Satset validates incoming data before it reaches a game listener: -1. **OOB Shielding (`pcall`)**: Packet decoding runs inside a protected call. If a malicious payload forces a read past the end of the buffer, the VM throws an error that is caught and silenced. -2. **Allocation Capping (`table.create`)**: Variable-length types (arrays, strings, maps) limit their allocations based on the remaining bytes in the payload. This prevents memory exhaustion and large GC spikes. -3. **Float Sanitization (`Sanitizer.sanitizeFloat`)**: All floating-point fields (`f32`, `f64`, `Vector3`, etc.) are clamped to prevent `NaN` and `Infinity` from propagating into game logic. -4. **Schema Verification (`Sanitizer.checkBounds`)**: For fixed-size schema fields, the serializer checks that enough bytes remain before executing the read. +1. **Out-of-bounds shielding (`pcall`)**: Packet decoding runs inside a protected call. If a payload forces a read past the end of the buffer, the VM throws and Satset drops the packet. +2. **Allocation caps**: Variable-length types, such as arrays, strings, and maps, cap allocations against the remaining bytes in the payload. +3. **Float sanitization (`Sanitizer.sanitizeFloat`)**: Floating-point fields (`f32`, `f64`, `Vector3`, etc.) clamp `NaN` and `Infinity` to `0`. +4. **Schema bounds checks (`Sanitizer.checkBounds`)**: Fixed-size schema reads check that enough bytes remain before reading. -## Batching Strategies +## Batching Satset's batching engine is configured through the `batching` table in `Satset.start()`. -### Default Runtime Settings - -The default runtime settings split reliable streams above 60 KB and send every ready batch during the frame flush. - -```luau -batching = { - reliableThreshold = 60000, -- Split reliable batches above 60 KB - maxPacketsPerFrame = 0, -- Send all ready batches each frame -} -``` - -`maxPacketsPerFrame` caps how many committed batches each queue can send in one frame. A value of `0` means no cap. - -### One-Commit Profile +### Runtime Defaults -Setting `reliableThreshold` to `0` disables threshold splitting for reliable streams. Queued reliable packets are committed once at the frame flush. +The current default commits reliable traffic at the frame flush. It does not split reliable traffic by size unless you opt into a positive `reliableThreshold`. ```luau batching = { - reliableThreshold = 0, -- Disable threshold splitting - maxPacketsPerFrame = 0, + reliableThreshold = 0, -- Commit reliable traffic at frame flush + unreliableThreshold = 900, -- Split unreliable traffic around the safe payload size + maxPacketsPerFrame = 0, -- No per-frame send cap } ``` -> [!NOTE] -> The current benchmark latency profile uses this setting. It reduces commit count for large homogeneous payloads, which reduces measured `Stats.DataSendKbps` in the benchmark. It also allows larger `RemoteEvent` payloads, so test it with real game payloads before use. +`maxPacketsPerFrame` caps how many committed batches each queue can send in one frame. A value of `0` means no cap. The benchmark uses the defaults above. diff --git a/docs/guide/channels.md b/docs/guide/channels.md index ec69904..7507c34 100644 --- a/docs/guide/channels.md +++ b/docs/guide/channels.md @@ -1,10 +1,10 @@ # Channels -Channels are for **stateful** synchronization. Unlike packets, which represent an *event*, a channel represents the *state* of an object. +Channels are Satset's state sync path. A packet represents an event. A channel represents the current state of an object. ## Delta Compression -Channels only send what has changed. When you update a field in a channel, Satset uses a bitmask to identify only the modified fields and sends only those bytes. +Channels send a full keyframe first. After that, `:set()` marks fields in a 32-bit dirty bitmask and the next flush sends only those field bytes. Periodic keyframes reset drift after dropped unreliable updates. ## Definition @@ -27,14 +27,14 @@ local PlayerState = Satset.defineChannel({ ## Updating State (Server) ```luau --- Create a stateful entity for a player +-- Create state for a player local entity = PlayerState:create(player.UserId, { health = 100, mana = 50, }) --- Later, updating only health -entity:set("health", 95) -- Only the 1-byte health field is transmitted! +-- The next flush sends only the 1-byte health field +entity:set("health", 95) ``` ## Reading State (Client) @@ -46,9 +46,9 @@ end) ``` > [!NOTE] -> Channels are optimized for **zero-allocation** updates. When you call `:set()`, Satset writes directly into a pre-allocated buffer and marks a bitmask. No tables are created during the sync process. +> Channels write server-side state into a buffer and mark a bitmask. Client subscribers still receive a normal table, because that is the public API boundary. ## Constraints - **Fixed-size only**: Channels do not support variable-length types like strings or arrays. -- **32 Field Limit**: Due to the 32-bit bitmask used for dirty tracking, a single channel can have at most 32 fields. +- **32 field limit**: A single channel can have at most 32 fields because dirty tracking uses one 32-bit mask. diff --git a/docs/guide/development-patterns.md b/docs/guide/development-patterns.md index aa95572..a104a96 100644 --- a/docs/guide/development-patterns.md +++ b/docs/guide/development-patterns.md @@ -1,53 +1,59 @@ # Development Patterns -Satset follows strict performance and safety constraints. These patterns keep the library predictable under high-throughput conditions. +Satset code should stay predictable under high packet volume. Prefer small changes that preserve the current buffer pipeline. -## Allocation-aware hot path +## Allocation-Aware Hot Path The send path avoids per-packet queue tables. Satset writes payloads directly into Luau `buffer` streams and commits exact-size buffers at flush time. -* **The Problem**: Table and string allocations can trigger GC pauses. When syncing hundreds of entities, those pauses can reduce frame rate. -* **The Pattern**: Reuse working buffers on the send path. Decode into tables only at the public API boundary, where packet listeners and channel subscribers receive Lua values. +Reuse working buffers on the send path. Decode into tables only at the public API boundary, where packet listeners and channel subscribers receive Luau values. -## Deterministic byte alignment +## Deterministic Byte Alignment Satset doesn't transmit type metadata or field names. Server and client must share an identical understanding of the buffer layout. -* **The Pattern**: `SchemaCompiler` sorts field definitions alphabetically before calculating memory offsets. This produces the same binary schema across environments, regardless of how the Luau table is ordered. +`SchemaCompiler` sorts field definitions alphabetically before calculating offsets. Server and client then produce the same binary schema, regardless of Luau table order. -## Mandatory input sanitization +## Input Sanitization Network data is untrusted. All numeric inputs must be validated before they reach the server state. -* **The Problem**: Malformed packets with `NaN` or `Infinity` can corrupt physics and state calculations. -* **The Pattern**: All floating-point types pass through `Sanitizer.sanitizeFloat()`. Values that fail validation are clamped to `0`. +Malformed packets with `NaN` or `Infinity` can corrupt physics and state calculations. Floating-point types pass through `Sanitizer.sanitizeFloat()`. Invalid values become `0`. -## Bit-density optimization +## Bit Density Choose data types based on bit-density. -* **The Pattern**: Use the smallest applicable type (e.g., `u8` for values 0-255). Use quantized types like `Vector3Quantized` for spatial data when full float precision isn't necessary. +Use the smallest applicable type, such as `u8` for values from 0 to 255. Use quantized types like `Vector3Quantized` for spatial data when full float precision is unnecessary. -## Explicit delta tracking +## Explicit Delta Tracking -State sync is an explicit, bitmask-tracked process, not an automatic "magic" sync. +State sync is an explicit, bitmask-tracked process. -* **The Pattern**: `Channels` track modified fields using a 32-bit mask. Only dirty fields are sent. Syncing happens during `PostSimulation`, the same frame phase used by packet batching. +`Channel` tracks modified fields using a 32-bit mask. Only dirty fields are sent after the first keyframe. Syncing happens during `PostSimulation`, the same frame phase used by packet batching. -## Defensive buffer reads +## Reliable Batch Compaction + +Reliable packet batches are allowed to change shape before they hit `RemoteEvent`. + +Repeated packet ids can become a grouped run. Direct reliable batches with the same byte length can be XORed against the previous batch for that peer. Broadcast reliable traffic stays raw because there is no single receiver state to track. + +Keep the header flags explicit. The high bits of the reliable header carry tracking and delta state; the low 14 bits remain the item count. + +## Defensive Buffer Reads Every buffer read includes a bounds check. -* **The Pattern**: The `Serializer` verifies buffer length before every read. Out-of-bounds attempts throw errors immediately to prevent undefined behavior. +`Serializer` verifies buffer length before reads. Out-of-bounds attempts throw and are caught by the packet dispatch protected call. ## Naming conventions -Satset uses a specific naming convention to maintain code clarity across the library: +Satset uses these naming conventions: -* **PascalCase**: Used for Modules, Class definitions, and Luau Types (e.g., `SchemaCompiler`, `SatsetConfig`). -* **camelCase**: Used for public API methods, local variables, and object properties (e.g., `definePacket`, `channelId`, `maxTokens`). -* **_camelCase** (Leading underscore): Used for internal/private state or functions that should not be accessed by the public API (e.g., `_guard`, `_applyUpdate`). -* **SCREAMING_SNAKE_CASE**: Used for constants and environment flags (e.g., `IS_SERVER`, `MTU_LIMIT`). +* **PascalCase**: Modules, class-like tables, and Luau types, such as `SchemaCompiler` and `SatsetConfig`. +* **camelCase**: Public API methods, locals, and object fields, such as `definePacket`, `channelId`, and `maxTokens`. +* **_camelCase**: Internal state or functions outside the public API, such as `_guard` and `_applyUpdate`. +* **SCREAMING_SNAKE_CASE**: Constants and environment flags, such as `IS_SERVER`. --- diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 3bde5ba..235d784 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -1,6 +1,6 @@ # Getting Started -Setting up Satset is straightforward. You need to start the engine on both the server and the client before using it. +Start Satset once on both the server and the client before defining packets or channels. ## Initialization @@ -11,17 +11,20 @@ local Satset = require(game:GetService("ReplicatedStorage").Packages.Satset) Satset.start({ guard = { - maxTokens = 60, - refillRate = 30, + maxTokens = 1000, + refillRate = 500, studioBypass = true -- Enabled by default }, batching = { - reliableThreshold = 60000, -- Split reliable batches above 60 KB - maxPacketsPerFrame = 0, -- Send all ready batches each frame + reliableThreshold = 0, -- Commit reliable traffic at the frame flush + unreliableThreshold = 900, -- Keep unreliable batches below the safe payload size + maxPacketsPerFrame = 0, -- No per-frame send cap } }) ``` +You can omit the config table and use the same defaults. + ## Your First Packet Packets are for stateless, fire-and-forget data. Define them in a shared script: diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 73ba5d5..452d6eb 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -1,6 +1,6 @@ # Installation -Satset can be installed using Wally, the most popular package manager for Roblox. +Install Satset with Wally, a model file, or Rojo. ## Wally diff --git a/docs/guide/introduction.md b/docs/guide/introduction.md index c4d71d2..1229673 100644 --- a/docs/guide/introduction.md +++ b/docs/guide/introduction.md @@ -1,6 +1,6 @@ # Introduction -**Satset** is a buffer-backed hybrid networking library for Roblox. It provides stateless packets, stateful channels, schema-based serialization, and server-side rate limiting. +**Satset** is a buffer-backed networking library for Roblox. It provides stateless packets, stateful channels, schema-based serialization, and server-side rate limiting. ## Why Satset? @@ -8,10 +8,10 @@ Roblox's native `RemoteEvent` system can become expensive for high-frequency dat - **Buffer-backed serialization**: Uses Luau `buffer` for compact binary payloads. - **Automatic batching**: Queues packet fires and commits them during `PostSimulation`. -- **Stateful Channels**: Efficiently synchronizes state changes using bitmask-based delta compression. +- **Stateful channels**: Syncs state changes through bitmask-based dirty fields and periodic keyframes. - **Guard and sanitization**: Applies server-side token buckets, buffer bounds checks, and float sanitization. - **Plain Luau API**: Works without a code generation step. ## "Sat set, sampai." -The name comes from Indonesian slang meaning "quick and efficient." Satset aims to reduce per-event overhead while keeping the API direct. +The name comes from Indonesian slang for doing something quickly. Satset aims to reduce per-event overhead while keeping the API direct. diff --git a/docs/guide/packets.md b/docs/guide/packets.md index 8e13df2..ab98eb8 100644 --- a/docs/guide/packets.md +++ b/docs/guide/packets.md @@ -1,16 +1,21 @@ # Packets -Packets are the primary way to send data in Satset. They are designed for **stateless** communication: you send a piece of data, and the receiver handles it. +Packets are Satset's stateless event path. You send a schema-backed payload, and the receiver handles the decoded table. ## Automatic Batching When you call `:fireServer()` or `:fireClient()`, Satset queues the encoded payload instead of sending it immediately. During `PostSimulation`, each queue is committed to one or more buffers and sent through the matching remote. -Reliable batches split at `reliableThreshold`. Unreliable batches split around 900 bytes by default. Fixed-size packet schemas omit payload-size headers. +Reliable traffic is committed at the frame flush by default. Direct reliable traffic also gets two wire-format passes before send: + +- same-packet runs share one packet id and one run count; +- same-size batches are XORed against the previous batch for that peer. + +Unreliable traffic still splits around 900 bytes by default. Fixed-size packet schemas omit payload-size headers. ## Reliability -By default, packets are **reliable** (order and delivery are guaranteed). You can make a packet **unreliable** by setting the `reliable` flag: +By default, packets are **reliable**. You can make a packet **unreliable** by setting the `reliable` flag: ```lua local PositionUpdate = Satset.definePacket({ @@ -24,11 +29,11 @@ local PositionUpdate = Satset.definePacket({ ## Schemas -Packets require a schema to know how to pack and unpack data. Satset's `Types` module provides a wide variety of optimized types: +Packets require a schema to pack and unpack data. Satset's `Types` module provides: - **Primitives**: `u8`, `u16`, `u32`, `i8`, `i16`, `i32`, `f32`, `f64`, `bool`, `u4`. - **Composites**: `array(type)`, `optional(type)`, `map(keyType, valType)`, `enum(values)`. - **Roblox**: `Vector3`, `Vector2`, `Color3`, `CFrame`. -- **Optimized**: `Vector3Quantized`, `Vector2Quantized`, `CFrame` (18-byte compressed). +- **Compact forms**: `Vector3Quantized`, `Vector2Quantized`, `CFrame` (18-byte compressed). Check the [Types API](../api/types.md) for more details. diff --git a/docs/guide/security.md b/docs/guide/security.md index 8dc6096..7800a93 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -4,23 +4,23 @@ Satset treats incoming network data as untrusted. The server validates payloads ## The Guard -The **Guard** is a token bucket rate limiter that is automatically enabled on the server. It tracks how many packets each player is sending and drops anything that exceeds the limit. +The **Guard** is a token bucket rate limiter used by the server. It tracks how many packets each player sends and drops anything above the limit. You can configure it during initialization: ```luau Satset.start({ guard = { - maxTokens = 60, -- Max burst capacity - refillRate = 30, -- Tokens refilled per second + maxTokens = 1000, -- Max burst capacity + refillRate = 500, -- Tokens refilled per second studioBypass = true -- Disable rate limiting in Studio (Default: true) } }) ``` -- **maxTokens**: The maximum tokens a player can hold. We enforce a minimum of 1 to prevent misconfiguration from breaking the network. +- **maxTokens**: The maximum tokens a player can hold. Satset clamps this to at least 1. - **refillRate**: Tokens added to the bucket every second. -- **studioBypass**: Skips rate limiting in Roblox Studio so you can stress test locally. The server ignores this setting in published games. +- **studioBypass**: Skips rate limiting in Roblox Studio so you can stress test locally. Published servers always use the bucket. When a player exceeds their limit, the Guard drops the packet silently. We do not print warnings to the console, which prevents exploiters from weaponizing log messages to cause server lag. @@ -30,7 +30,7 @@ We treat all incoming client data as hostile. If an exploiter sends a corrupted - **OOB Shielding**: We wrap packet decoding in a `pcall`. If a malicious client truncates a buffer to force an out-of-bounds read, the operation fails silently. The server drops the packet without printing stack traces to the console. - **Allocation Capping**: When reading variable-length types like arrays or strings, we cap the `table.create` allocation to the actual remaining bytes in the buffer. If an exploiter sends a 4-byte payload claiming to contain 65,000 elements, Satset limits the array size to match the buffer. This stops memory exhaustion and garbage collector spikes. -- **Float Sanitization**: We clamp `NaN` and `±Infinity` to `0` when reading or writing floating-point numbers (`f32`, `f64`, `Vector3`). This prevents corrupted math from infecting the server state. +- **Float sanitization**: Satset clamps `NaN` and `±Infinity` to `0` when reading or writing floating-point numbers (`f32`, `f64`, `Vector3`). This keeps corrupted math out of game state. ## Listener Protection @@ -44,4 +44,4 @@ One broken game callback does not stop packet or channel dispatch. ## Stateless Design -Because packets are defined with a fixed schema, clients cannot send arbitrary keys or nested tables that might cause memory exhaustion or CPU spikes on the server. +Packets use fixed schemas. Clients cannot send arbitrary keys or nested tables into packet listeners.