chi0sk
github.com/chi0sk

NetReplication

A production-ready network replication system for Roblox that enables secure, efficient server-to-client state synchronization with built-in integrity verification and delta compression. Heavily inspired by loleris's Replica

Delta Compression

Only send what changed to minimize bandwidth usage

Data Integrity

xxHash64 verification detects corruption and tampering

Security First

Client-side writes are blocked and violations are logged

Automatic Recovery

State refresh and retry mechanisms handle network issues

Why NetReplication?

NetReplication provides enterprise-grade features for state management:

  • Reliability: Automatic retries, state refresh, and gap detection
  • Security: Read-only clients, session keys, and violation tracking
  • Performance: Batched updates, delta compression, and configurable replication rates
  • Developer Experience: Comprehensive hooks, simulation mode, and observability
  • Battle-Tested: Handles packet loss, corruption, desyncs, and malicious clients

Getting Started

Installation

Copy the NetReplication module into your game's ReplicatedStorage.

Basic Server Usage

local NetReplication = require(game.ReplicatedStorage.NetReplication)

-- Initialize on the server
NetReplication.Initialize({
    ObservabilityEnabled = true,
})

-- Create a replica
local gameStateReplica = NetReplication.NewReplica({
    InitialState = {
        RoundNumber = 0,
        TimeLeft = 60,
        Players = {},
    },
    ReplicationRate = 20, -- 20 updates per second
})

-- Update the state
gameStateReplica:Set({"RoundNumber"}, 1)
gameStateReplica:Set({"TimeLeft"}, 55)

-- Clean up when done
gameStateReplica:Destroy()

Basic Client Usage

local NetReplication = require(game.ReplicatedStorage.NetReplication)

-- Subscribe to a replica (token obtained from server)
local replica = NetReplication.Subscribe(token, {
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            print("State changed:", path, oldValue, "->", newValue)
        end,
    },
})

if replica then
    -- Read the state
    local roundNumber = replica:Get({"RoundNumber"})
    print("Current round:", roundNumber)
else
    warn("Failed to subscribe to replica")
end

Complete Example: Game State Replication

-- Server Script
local NetReplication = require(game.ReplicatedStorage.NetReplication)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

NetReplication.Initialize()

local gameState = NetReplication.NewReplica({
    InitialState = {
        Status = "Waiting",
        Round = 0,
        Players = {},
        TimeLeft = 0,
    },
    ReplicationRate = 10,
    UseDeltaCompression = true,
})

-- Share the token with clients
local tokenValue = Instance.new("StringValue")
tokenValue.Name = "GameStateToken"
tokenValue.Value = gameState.Token
tokenValue.Parent = ReplicatedStorage

-- Update game state
while true do
    task.wait(1)
    local timeLeft = gameState:Get({"TimeLeft"})
    gameState:Set({"TimeLeft"}, math.max(0, timeLeft - 1))
end
-- Client Script
local NetReplication = require(game.ReplicatedStorage.NetReplication)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local tokenValue = ReplicatedStorage:WaitForChild("GameStateToken")
local token = tokenValue.Value

local gameState = NetReplication.Subscribe(token, {
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            if path[1] == "TimeLeft" then
                print("Time left:", newValue)
            elseif path[1] == "Status" then
                print("Game status:", newValue)
            end
        end,
    },
})

if gameState then
    print("Connected to game state!")
    print("Current status:", gameState:Get({"Status"}))
end

Core Concepts

Replicas

A replica is a server-authoritative data container that automatically replicates to subscribed clients. The server creates replicas and updates them, while clients subscribe to receive updates.

Key Properties:

  • Token: Unique identifier for the replica
  • Data: The current state (nested tables supported)
  • Version: Monotonically increasing version number
  • Subscribers: Clients currently receiving updates

Replication Flow

NetReplication uses a batched replication model:

  1. Server Updates: Call Replica:Set() to queue changes
  2. Batching: Changes accumulate until the next replication tick
  3. Transmission: Batch is sent to all subscribers with version and hash
  4. Client Application: Clients verify integrity and apply changes
  5. Hooks: OnChange callbacks fire for updated values
Note: The replication rate determines how frequently batches are sent. Higher rates mean lower latency but more network overhead.

Delta Compression

When enabled, NetReplication sends only the changed values instead of the entire state:

  • Full State Mode: Sends complete data snapshot every update
  • Delta Mode: Sends only path-value pairs that changed
  • Threshold: Automatically switches based on data size
local replica = NetReplication.NewReplica({
    InitialState = { ... },
    UseDeltaCompression = true,
    DeltaCompressionThreshold = 10240, -- 10KB threshold
})

Data Integrity

Every update includes an xxHash64 checksum of the complete state. Clients verify this hash to detect:

  • Network corruption
  • Packet manipulation
  • Desynchronization

When integrity checks fail, the client automatically requests a state refresh from the server.

-- Integrity is automatic, but you can check desync status
if replica:IsDesynced() then
    warn("Replica is permanently desynced - consider recreating")
end

API Reference

NetReplication.Initialize

NetReplication.Initialize(config: InitConfig?)

Initializes the NetReplication system on server or client. Must be called before using other functions.

Parameters

Parameter Type Description
config InitConfig? Optional configuration table

Config Options

Field Type Default Description
SimulationMode boolean false Enable network simulation for testing
SimulationConfig SimulationConfig nil Latency, packet loss, corruption settings
ObservabilityEnabled boolean false Enable detailed event logging

Example

NetReplication.Initialize({
    ObservabilityEnabled = true,
    SimulationMode = game:GetService("RunService"):IsStudio(),
    SimulationConfig = {
        LatencyMs = 100,
        PacketLoss = 0.05,
        CorruptionRate = 0.01,
    },
})

NetReplication.NewReplica (Server Only)

NetReplication.NewReplica(config: ReplicaConfig): Replica

Creates a new replica on the server.

Parameters

Parameter Type Description
config ReplicaConfig Configuration for the replica

Config Options

Field Type Default Description
InitialState table required Starting state of the replica
ReplicationRate number 10 Updates per second (max 20 by default)
UseDeltaCompression boolean false Send only changed values
DeltaCompressionThreshold number 10240 Minimum bytes to use delta mode
Hooks table nil Event callbacks

Returns

A new Replica object with Token, Data, and methods.

Example

local replica = NetReplication.NewReplica({
    InitialState = {
        Health = 100,
        Position = Vector3.new(0, 10, 0),
        Inventory = {},
    },
    ReplicationRate = 15,
    UseDeltaCompression = true,
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            print("Changed:", table.concat(path, "."), oldValue, newValue)
        end,
        OnDestruction = function(replica)
            print("Replica destroyed:", replica.Token)
        end,
    },
})

NetReplication.Subscribe (Client Only)

NetReplication.Subscribe(token: string, config: ReplicaConfig?): Replica?

Subscribes to a server replica. Yields until subscription completes or times out.

Parameters

Parameter Type Description
token string Replica token from server
config ReplicaConfig? Optional config (mainly for hooks)

Returns

A client Replica object, or nil if subscription failed.

Example

local replica = NetReplication.Subscribe(token, {
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            -- Update UI
        end,
        OnDestruction = function()
            warn("Server destroyed replica")
        end,
    },
})

if not replica then
    warn("Failed to subscribe")
end

Replica:Set (Server Only)

Replica:Set(path: {string}, value: any)

Updates a value in the replica. Changes are queued and sent in the next batch.

Parameters

Parameter Type Description
path {string} Array of keys to nested value
value any New value to set

Example

-- Set top-level value
replica:Set({"Health"}, 75)

-- Set nested value
replica:Set({"Player", "Stats", "Level"}, 5)

-- Set array element
replica:Set({"Inventory", 1}, "Sword")

Replica:Get

Replica:Get(path: {string}): any

Retrieves a value from the replica. Works on both server and client.

Parameters

Parameter Type Description
path {string} Array of keys to nested value

Returns

The value at the path, or nil if not found.

Example

local health = replica:Get({"Health"})
local level = replica:Get({"Player", "Stats", "Level"})
local firstItem = replica:Get({"Inventory", 1})

Replica:Destroy (Server Only)

Replica:Destroy()

Destroys the replica and notifies all subscribers. Cannot be undone.

Example

-- Clean up when no longer needed
replica:Destroy()

Other Methods

Replica:GetSubscriberCount (Server Only)

Replica:GetSubscriberCount(): number

Returns the number of clients currently subscribed.

Replica:IsDesynced (Client Only)

Replica:IsDesynced(): boolean

Returns true if the replica has permanently desynced and retry limit was exceeded.

Configuration

Replica Configuration

ReplicationRate

Controls how many times per second updates are sent to clients.

local replica = NetReplication.NewReplica({
    InitialState = { ... },
    ReplicationRate = 20, -- 20 updates per second
})

-- Lower rate = less bandwidth, higher latency
-- Higher rate = more bandwidth, lower latency
-- Max rate depends on BATCH_LOOP_INTERVAL (default: 20hz)
Warning: Replication rates exceeding the batch loop maximum will be clamped. Modify BATCH_LOOP_INTERVAL in the source to support higher rates.

Delta Compression

Reduces bandwidth by sending only changed values instead of the full state.

local replica = NetReplication.NewReplica({
    InitialState = { ... },
    UseDeltaCompression = true,
    DeltaCompressionThreshold = 5120, -- Only use delta if state > 5KB
})

When to use delta compression:

  • Large state objects with infrequent changes
  • Many subscribers receiving the same updates
  • Bandwidth-constrained environments

When to avoid delta compression:

  • Small state objects (overhead outweighs benefit)
  • Frequent full-state changes
  • Very high replication rates

Hooks

Hooks allow you to respond to replication events on both server and client.

OnChange (Server & Client)

Hooks = {
    OnChange = function(path, oldValue, newValue)
        print("Path:", table.concat(path, "."))
        print("Old:", oldValue, "New:", newValue)
    end,
}

Called whenever a value changes. On the client, this fires after successful integrity verification. An empty path {} indicates a full state replacement.

OnDestruction (Server & Client)

Hooks = {
    OnDestruction = function(replica)
        print("Replica destroyed:", replica.Token)
        -- Clean up UI, stop listening, etc.
    end,
}

OnSecurityViolation (Server Only)

Hooks = {
    OnSecurityViolation = function(clientId, violationType, details)
        warn("Security violation from client:", clientId)
        warn("Type:", violationType)
        warn("Details:", details)
        
        -- Log to analytics, ban player, etc.
    end,
}

Called when a client attempts to modify the replica or when integrity checks fail. After 5 violations in 60 seconds, the client is automatically kicked.

Simulation Mode

Simulation mode allows testing network conditions without a live server.

NetReplication.Initialize({
    SimulationMode = true,
    SimulationConfig = {
        LatencyMs = 150,        -- Add 150ms delay
        PacketLoss = 0.1,       -- Drop 10% of packets
        CorruptionRate = 0.05,  -- Corrupt 5% of packets
    },
})

Use cases:

  • Testing recovery mechanisms
  • Simulating poor network conditions
  • Validating integrity checks
  • Stress testing state refresh
Warning: Never enable simulation mode in production. Corruption and packet loss are simulated on the server and affect all clients.

Advanced Features

Security & Validation

NetReplication implements multiple security layers:

Read-Only Clients

Clients cannot call Set() on replicas. Attempts are blocked and reported to the server:

-- This will fail on the client and trigger a security event
replica:Set({"Health"}, 9999)

Session Keys

Each subscription receives a unique session key to prevent replay attacks and unauthorized access.

Integrity Verification

Every batch includes an xxHash64 checksum. Clients verify the hash after applying changes. Mismatches trigger automatic state refresh.

Rate Limiting

Clients are rate-limited on subscription and refresh requests to prevent abuse:

  • Subscribe: Max 10 attempts per second
  • Refresh: Max 5 attempts per 2 seconds

Violation Tracking

The server tracks security violations per client. After 5 violations in 60 seconds, the client is kicked:

Hooks = {
    OnSecurityViolation = function(clientId, violationType, details)
        -- violationType can be:
        -- "client_attempted_set" - client tried to modify replica
        -- "hash_mismatch" - integrity check failed
        -- other custom violation types
        
        if violationType == "client_attempted_set" then
            -- Log exploit attempt
        end
    end,
}

Batch Replication

NetReplication batches multiple Set() calls into a single network message:

  1. Server updates accumulate in a pending queue
  2. Every replication tick, changes are coalesced by path
  3. Latest value for each path is sent (older values are dropped)
  4. Single RemoteEvent fires to all subscribers
-- These three calls are batched into one network message
replica:Set({"Player", "Health"}, 90)
replica:Set({"Player", "Mana"}, 50)
replica:Set({"Player", "Health"}, 85) -- Overwrites first Health update

-- Client receives one batch with:
-- {"Player", "Health"} = 85 (latest)
-- {"Player", "Mana"} = 50

Failed Batch Retry

If a batch fails to send, it's automatically retried up to 3 times with the same data.

State Refresh

When clients detect issues, they can request a full state refresh from the server:

Automatic Triggers

  • Integrity failure: Hash mismatch after applying changes
  • Sequence gap: Received version is too far ahead (gap > 5)
  • Repeated corruption: 3+ integrity failures
  • Repeated gaps: 5+ sequence gaps

Refresh Process

  1. Client sends RefreshState request to server
  2. Server responds with complete current state, version, and hash
  3. Client verifies hash and replaces local state
  4. OnChange hook fires with empty path to signal full replacement
-- Check if client is permanently desynced
if replica:IsDesynced() then
    warn("Replica failed to resync after max retries")
    -- Consider destroying and resubscribing
end

Exponential Backoff

Failed refresh attempts retry with exponential backoff up to 5 times. After that, the replica is marked as permanently desynced.

Observability

Enable observability to log detailed replication events:

NetReplication.Initialize({
    ObservabilityEnabled = true,
})

Logged Events

  • ReplicaCreated: New replica created with token
  • ReplicaDestroyed: Replica destroyed with subscriber count
  • ClientSubscribed: Client subscribed with version
  • BatchReplicated: Batch sent with change count and delta mode
  • BatchApplied: Client applied batch successfully
  • StateRefreshed: State refresh completed
  • SecurityViolation: Client triggered security event
  • PlayerKicked: Client kicked for repeated violations

All events are printed as JSON with timestamp, event name, and details.

Best Practices

Choose Appropriate Replication Rates

Match the replication rate to your use case:

  • High-frequency (15-20hz): Fast-paced gameplay, player movement
  • Medium-frequency (5-10hz): Game state, round timers, leaderboards
  • Low-frequency (1-2hz): Lobby state, configuration, announcements

Use Delta Compression Wisely

Enable delta compression for large state objects where most of the data stays constant. Avoid it for small states or when everything changes frequently.

Structure Your State Efficiently

-- Good: Flat structure for frequently updated values
{
    RoundNumber = 1,
    TimeLeft = 60,
    Status = "Active",
}

-- Bad: Deep nesting for simple values
{
    Game = {
        Round = {
            Number = 1,
            Timer = { TimeLeft = 60 },
        },
    },
}

Batch Related Changes

-- Good: Multiple changes in one tick get batched
replica:Set({"Player", "Health"}, 100)
replica:Set({"Player", "Mana"}, 100)
replica:Set({"Player", "Level"}, 5)

-- Bad: Waiting between changes wastes updates
replica:Set({"Player", "Health"}, 100)
task.wait(0.1)
replica:Set({"Player", "Mana"}, 100) -- Separate batch

Handle Subscription Failures

local replica = NetReplication.Subscribe(token, config)

if not replica then
    warn("Failed to subscribe - falling back to local mode")
    -- Create local fallback state
    -- or retry after delay
    return
end

-- Check for desync during gameplay
if replica:IsDesynced() then
    warn("Replica desynced - resubscribing")
    replica = NetReplication.Subscribe(token, config)
end

Clean Up Properly

-- Server: Destroy replicas when no longer needed
game:BindToClose(function()
    for _, replica in pairs(activeReplicas) do
        replica:Destroy()
    end
end)

-- Client: Handle destruction gracefully
Hooks = {
    OnDestruction = function()
        -- Clear UI
        -- Stop listening
        -- Remove references
    end,
}

Use Hooks for Side Effects

-- Good: Use hooks to update UI
Hooks = {
    OnChange = function(path, oldValue, newValue)
        if path[1] == "TimeLeft" then
            updateTimerUI(newValue)
        end
    end,
}

-- Bad: Polling the state
while true do
    local timeLeft = replica:Get({"TimeLeft"})
    updateTimerUI(timeLeft)
    task.wait(0.1)
end

Monitor Security Violations

Hooks = {
    OnSecurityViolation = function(clientId, violationType, details)
        -- Log to analytics
        logSecurityEvent(clientId, violationType, details)
        
        -- Take action for severe violations
        if violationType == "client_attempted_set" then
            -- Client is trying to exploit
            -- Consider immediate ban
        end
    end,
}

Test with Simulation Mode

-- Enable in Studio to test recovery
if game:GetService("RunService"):IsStudio() then
    NetReplication.Initialize({
        SimulationMode = true,
        SimulationConfig = {
            LatencyMs = 200,
            PacketLoss = 0.1,
            CorruptionRate = 0.05,
        },
    })
end

Examples

Example 1: Round-Based Game State

-- Server
local NetReplication = require(game.ReplicatedStorage.NetReplication)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

NetReplication.Initialize()

local gameState = NetReplication.NewReplica({
    InitialState = {
        Status = "Waiting",
        Round = 0,
        Players = {},
        TimeLeft = 0,
        Winner = nil,
    },
    ReplicationRate = 5,
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            if path[1] == "Status" then
                print("Game status changed to:", newValue)
            end
        end,
    },
})

-- Share token with clients
local token = Instance.new("StringValue")
token.Name = "GameStateToken"
token.Value = gameState.Token
token.Parent = ReplicatedStorage

-- Game loop
local function startRound()
    gameState:Set({"Status"}, "Active")
    gameState:Set({"Round"}, gameState:Get({"Round"}) + 1)
    gameState:Set({"TimeLeft"}, 120)
    
    while gameState:Get({"TimeLeft"}) > 0 do
        task.wait(1)
        gameState:Set({"TimeLeft"}, gameState:Get({"TimeLeft"}) - 1)
    end
    
    gameState:Set({"Status"}, "Ended")
    gameState:Set({"Winner"}, determineWinner())
    task.wait(10)
end

while true do
    startRound()
end
-- Client
local NetReplication = require(game.ReplicatedStorage.NetReplication)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local token = ReplicatedStorage:WaitForChild("GameStateToken").Value

local gameState = NetReplication.Subscribe(token, {
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            if path[1] == "Status" then
                if newValue == "Active" then
                    showGameUI()
                elseif newValue == "Ended" then
                    showResultsUI()
                end
            elseif path[1] == "TimeLeft" then
                updateTimer(newValue)
            elseif path[1] == "Winner" then
                displayWinner(newValue)
            end
        end,
    },
})

if gameState then
    print("Connected to game state")
    print("Current round:", gameState:Get({"Round"}))
    print("Status:", gameState:Get({"Status"}))
end

Example 2: Player Leaderboard

-- Server
local leaderboard = NetReplication.NewReplica({
    InitialState = {
        Players = {},
        LastUpdate = os.time(),
    },
    ReplicationRate = 2, -- Update twice per second
    UseDeltaCompression = true,
})

-- Update player score
local function updateScore(userId, score)
    leaderboard:Set({"Players", tostring(userId)}, {
        UserId = userId,
        Score = score,
        Timestamp = os.time(),
    })
    leaderboard:Set({"LastUpdate"}, os.time())
end

-- Add new player
game.Players.PlayerAdded:Connect(function(player)
    updateScore(player.UserId, 0)
end)

-- Remove player
game.Players.PlayerRemoving:Connect(function(player)
    leaderboard:Set({"Players", tostring(player.UserId)}, nil)
end)

Example 3: Character Health Replication

-- Server
local characterReplicas = {}

game.Players.PlayerAdded:Connect(function(player)
    local replica = NetReplication.NewReplica({
        InitialState = {
            UserId = player.UserId,
            Health = 100,
            MaxHealth = 100,
            Position = Vector3.new(0, 10, 0),
            State = "Idle",
        },
        ReplicationRate = 15, -- 15hz for smooth updates
        UseDeltaCompression = true,
    })
    
    characterReplicas[player.UserId] = replica
    
    -- Share token with that specific player
    local token = Instance.new("StringValue")
    token.Name = "CharacterToken"
    token.Value = replica.Token
    token.Parent = player
    
    player.CharacterAdded:Connect(function(character)
        local humanoid = character:WaitForChild("Humanoid")
        
        humanoid.HealthChanged:Connect(function(health)
            replica:Set({"Health"}, health)
        end)
        
        humanoid.Died:Connect(function()
            replica:Set({"State"}, "Dead")
        end)
        
        -- Update position periodically
        while humanoid.Health > 0 do
            task.wait(0.1)
            if character.PrimaryPart then
                replica:Set({"Position"}, character.PrimaryPart.Position)
            end
        end
    end)
end)

game.Players.PlayerRemoving:Connect(function(player)
    local replica = characterReplicas[player.UserId]
    if replica then
        replica:Destroy()
        characterReplicas[player.UserId] = nil
    end
end)
-- Client
local Players = game:GetService("Players")
local player = Players.LocalPlayer

local token = player:WaitForChild("CharacterToken").Value

local characterState = NetReplication.Subscribe(token, {
    Hooks = {
        OnChange = function(path, oldValue, newValue)
            if path[1] == "Health" then
                updateHealthBar(newValue)
            elseif path[1] == "State" then
                if newValue == "Dead" then
                    showDeathScreen()
                end
            end
        end,
    },
})

if characterState then
    print("Character state synced")
    print("Health:", characterState:Get({"Health"}))
end

Example 4: Global Configuration

-- Server
local config = NetReplication.NewReplica({
    InitialState = {
        DailyReward = 100,
        DoubleXPEnabled = false,
        MaintenanceMode = false,
        MOTD = "Welcome to the game!",
        EventActive = false,
    },
    ReplicationRate = 1, -- Only 1hz for config
})

-- Admin command to update config
local function setDoubleXP(enabled)
    config:Set({"DoubleXPEnabled"}, enabled)
    print("Double XP:", enabled)
end

-- All clients automatically receive the update

Troubleshooting

Subscription Fails

Symptom: Subscribe() returns nil

Possible Causes:

  • Invalid or expired token
  • Server replica was destroyed
  • Network remotes not set up correctly
  • Timeout (5 second default)

Solutions:

-- Wait for token to exist
local tokenValue = ReplicatedStorage:WaitForChild("GameStateToken", 10)
if not tokenValue then
    warn("Token not found")
    return
end

-- Verify token is valid
local token = tokenValue.Value
if token == "" then
    warn("Empty token")
    return
end

-- Try subscribing
local replica = NetReplication.Subscribe(token)
if not replica then
    warn("Failed to subscribe - retrying")
    task.wait(2)
    replica = NetReplication.Subscribe(token)
end

Client Desyncs

Symptom: Client state doesn't match server

Causes:

  • Packet loss (real or simulated)
  • Corruption (real or simulated)
  • State refresh failed

Solutions:

-- Check if permanently desynced
if replica:IsDesynced() then
    warn("Replica permanently desynced - resubscribing")
    replica = NetReplication.Subscribe(token)
end

-- Monitor with hooks
Hooks = {
    OnSecurityViolation = function(clientId, violationType, details)
        if violationType == "hash_mismatch" then
            warn("Integrity failure detected")
            -- State refresh will be automatic
        end
    end,
}

High Bandwidth Usage

Symptom: Network usage is too high

Solutions:

  • Lower the replication rate
  • Enable delta compression
  • Reduce state size
  • Batch more changes together
-- Before: High bandwidth
local replica = NetReplication.NewReplica({
    InitialState = { ... },
    ReplicationRate = 20, -- Too high for this data
    UseDeltaCompression = false,
})

-- After: Optimized
local replica = NetReplication.NewReplica({
    InitialState = { ... },
    ReplicationRate = 5, -- Lower rate
    UseDeltaCompression = true, -- Enable delta
    DeltaCompressionThreshold = 5120,
})

Stale Batches Warning

Symptom: "Stale update ignored" warnings in output

Cause: Client received an older version than current

This is usually harmless: The client ignores old updates automatically. However, frequent stale batches may indicate network issues.

Circuit Breaker Open (Future Feature)

Note: While circuit breaker hooks exist in the code, they're not fully implemented. This is a placeholder for future resilience features.

Security Violations

Symptom: OnSecurityViolation hook firing frequently

If violationType is "client_attempted_set":

  • A client is trying to modify the replica
  • Likely an exploit attempt
  • Client will be kicked after 5 violations in 60 seconds
Hooks = {
    OnSecurityViolation = function(clientId, violationType, details)
        if violationType == "client_attempted_set" then
            -- Log to analytics
            logExploitAttempt(clientId, details)
            
            -- Consider immediate ban
            local player = game.Players:GetPlayerByUserId(clientId)
            if player then
                player:Kick("Unauthorized modification attempt")
            end
        end
    end,
}

Getting Help

  • Enable observability to see detailed logs
  • Check all hooks are firing as expected
  • Test with simulation mode to isolate issues
  • Review the source code for implementation details
  • Contact @rituals._ on Discord for support

NetReplication is created and maintained by sam (@chi0sk)

Documentation last updated: February 2026