Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

ReplicatedRegistry2 is a client-server synchronized table registry module for Roblox that provides two-way data replication with built-in filtering, change tracking, and automatic synchronization.

Key Features

  • Two-way synchronization between server and clients
  • Change tracking with automatic diff calculation
  • Built-in filters for access control and rate limiting
  • Commit/revert system for managing changes
  • Proxy interface for clean, chainable API
  • Custom callbacks for validation and security

Basic Concept

ReplicatedRegistry2 maintains a registry of tables that can be synchronized between server and clients. Each registered table:

  • Has a unique key for identification
  • Tracks changes automatically
  • Can be replicated with filters
  • Supports both direct and proxy access patterns

When to Use

  • Player data synchronization
  • Shared game state (leaderboards, settings)
  • Real-time collaborative data
  • Any data that needs server-client sync with validation

Setup

Installation

Place the ReplicatedRegistry2 module in ReplicatedStorage or ServerScriptService depending on your project structure.

Remote Setup

ReplicatedRegistry2 requires two remotes for communication:

-- In ReplicatedStorage
local RequestFullRemote = Instance.new("RemoteFunction")
RequestFullRemote.Name = "RequestFull"
RequestFullRemote.Parent = ReplicatedStorage

local SendChangesRemote = Instance.new("RemoteEvent")
SendChangesRemote.Name = "SendChanges"
SendChangesRemote.Parent = ReplicatedStorage

Server Configuration

local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

-- Option 1: Using RemoteInstances directly
ReplicatedRegistry.server.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Option 2: Using custom callbacks (allows you to do modifications before send/recieve)
ReplicatedRegistry.server.set_remote_callbacks(
    function(handler)
        -- Initialize request handler
        ReplicatedStorage.RequestFullRemote.OnServerInvoke = handler
    end,
    function(player, key, changes)
        -- Send changes to client
        ReplicatedStorage.SendChangesRemote:FireClient(player, key, changes)
    end,
    function(receiver)
        -- Connect change receiver
        ReplicatedStorage.SendChangesRemote.OnServerEvent:Connect(receiver)
    end
)

Client Configuration

local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

-- Option 1: Using RemoteInstances directly
ReplicatedRegistry.client.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Option 2: Using custom callbacks (allows you to do modifications before send/recieve)
ReplicatedRegistry.client.set_remote_callbacks(
    function(key)
        -- Request full table
        return ReplicatedStorage.RequestFullRemote:InvokeServer(key)
    end,
    function(key, changes)
        -- Send changes to server
        ReplicatedStorage.SendChangesRemote:FireServer(key, changes)
    end,
    function(receiver)
        -- Connect change receiver
        ReplicatedStorage.SendChangesRemote.OnClientEvent:Connect(receiver)
    end
)

Server API

Registering Tables

server.register(key, table, filter?)

Registers a table on the server for replication.

local playerData = {
    coins = 100,
    level = 1,
    inventory = {}
}

ReplicatedRegistry.server.register("player_123", playerData)

With filter:

local filter = ReplicatedRegistry.get_filter({
    rate_limit = 10, -- 10 changes per second max
    player_whitelist = {123, 456} -- Only these user IDs
})

ReplicatedRegistry.server.register("shared_data", sharedTable, filter)

Accessing Tables

server.view(key)

Returns the registered table directly.

local data = ReplicatedRegistry.server.view("player_123")
data.coins = 150
data.level = 2

server.view_as_proxy(key)

Returns a proxy interface with helper methods.

local proxy = ReplicatedRegistry.server.view_as_proxy("player_123")

-- Set values
proxy.set({"coins"}, 200)
proxy.set({"inventory", "sword"}, true)

-- Get values
local coins = proxy.get({"coins"})

-- Increment values
proxy.incr({"coins"}, 50)
proxy.incr({"level"}, 1)

-- Replicate to specific players
proxy.replicate({player1, player2})

-- Replicate to all players
proxy.replicate()

-- Get full table
local fullTable = proxy.full()

Replication

server.to_clients(key, players, sender?, changes?, auto_commit?)

Sends changes to specified clients.

-- Replicate to specific players
ReplicatedRegistry.server.to_clients(
    "player_123",
    {player1, player2}
)

-- Replicate to all players
ReplicatedRegistry.server.to_clients(
    "global_data",
    game.Players:GetPlayers()
)

Change Management

get_changes(key)

Gets all pending changes since last commit.

local changes = ReplicatedRegistry.get_changes("player_123")
-- changes = {{v = 150, p = {"coins"}}, {v = 2, p = {"level"}}}

commit_changes(key, changes?)

Commits changes, updating the internal copy.

ReplicatedRegistry.commit_changes("player_123")

revert_changes(key, changes?)

Reverts uncommitted changes.

local data = ReplicatedRegistry.server.view("player_123")
data.coins = 999 -- Oops, mistake

ReplicatedRegistry.revert_changes("player_123")
-- coins reverted to previous committed value

Client API

Accessing Tables

client.view(key)

Requests and returns the registered table from the server.

local playerData = ReplicatedRegistry.client.view("player_123")
print(playerData.coins) -- 100

-- Modify locally
playerData.coins = 150

client.view_as_proxy(key)

Returns a proxy interface with helper methods.

local proxy = ReplicatedRegistry.client.view_as_proxy("player_123")

-- Set values
proxy.set({"coins"}, 200)
proxy.set({"inventory", "sword"}, true)

-- Get values
local coins = proxy.get({"coins"})

-- Increment values
proxy.incr({"coins"}, 50)

-- Replicate changes to server
proxy.replicate()

-- Get full table
local fullTable = proxy.full()

Replication

client.to_server(key, sender?, changes?, auto_commit?)

Sends changes to the server.

local data = ReplicatedRegistry.client.view("player_123")
data.coins = 200
data.level = 5

-- Send changes to server
ReplicatedRegistry.client.to_server("player_123")

Manual change tracking:

local data = ReplicatedRegistry.client.view("player_123")
data.coins = 200

local changes = ReplicatedRegistry.get_changes("player_123")
ReplicatedRegistry.client.to_server("player_123", nil, changes)

Listening for Changes

on_recieve(key, callback)

Listens for incoming changes from the server.

local connection = ReplicatedRegistry.on_recieve("player_123", function(sender, table, changes)
    print("Received changes from", sender)
    for _, change in changes do
        print("Path:", table.concat(change.p, "."))
        print("Value:", change.v)
    end
end)

-- Disconnect when done
connection:Disconnect()

Change Management

Same as server API:

  • get_changes(key) - Get pending changes
  • commit_changes(key) - Commit changes
  • revert_changes(key) - Revert changes

Filters

Filters are a on-recieved per register key validation system everytime your context recieves TableChanges from the opposite context.

Built-in Filters

player_blacklist

Blocks specific user IDs from sending changes.

local filter = ReplicatedRegistry.get_filter({
    player_blacklist = {123456, 789012}
})

ReplicatedRegistry.server.register("data", myTable, filter)

player_whitelist

Only allows specific user IDs to send changes.

local filter = ReplicatedRegistry.get_filter({
    player_whitelist = {123456, 789012}
})

ReplicatedRegistry.server.register("admin_data", adminTable, filter)

rate_limit

Limits how many changes per second can be received.

local filter = ReplicatedRegistry.get_filter({
    rate_limit = 5 -- Max 5 changes per second
})

ReplicatedRegistry.server.register("data", myTable, filter)

no_recieve

Prevents receiving changes entirely (send-only).

local filter = ReplicatedRegistry.get_filter({
    no_recieve = true
})

ReplicatedRegistry.server.register("readonly_data", myTable, filter)

Custom Filters

You can use custom validation logic.

local function filter(sender, register_key, tbl, changes)
    -- Only allow owner to modify their own data
    if sender.UserId ~= register_key then
        return false
    end
    for _, c in changes do
        local path, value = c.p, c.v
        -- Only allow positive coin values
        if path[1] == "coins" and value < 0 then
            return false
        end
    end
    
    return true
end

ReplicatedRegistry.server.register("player_data", data, filter)

Combining Filters

You can combine multiple filters together into a composite filter using get_filter().

local filter = ReplicatedRegistry.get_filter({
    player_whitelist = {123456, 789012},
    rate_limit = 10,
    custom = function(sender, _, _, changes)
        for _, c in changes do
            -- Additional validation
            if type(value) ~= "number" then return false end
        end
        return true
    end
})

Composite Filter Behavior

  • All filters in a composite filter must pass for a change to be accepted
  • If custom filter is provided, it runs after built-in filters
  • If any filter returns false, the change is rejected

Callbacks

Callbacks allow you to customize behavior at key points in the replication process.

Global Callbacks

on_changes_recieved

Called whenever changes are received from a remote.

ReplicatedRegistry.callbacks.on_changes_recieved = function(sender, register_key, changes)
    -- sender: Player who sent (nil on client)
    -- register_key: The key of the registree
    -- changes: Array of changes
    
    print(`Received {#changes} changes for {register_key}`)
    
    -- Return values:
    -- "pass" - Continue normal processing
    -- "return" - Stop processing, don't apply changes
    -- "disable" - Disable this key entirely
    
    return "pass"
end

With validation:

ReplicatedRegistry.callbacks.on_changes_recieved = function(sender, register_key, changes)
    -- Validate changes
    for _, change in changes do
        if not isValidChange(change) then
            return "disable", "Invalid change detected"
        end
    end
    
    return "pass"
end

on_key_disabled

Called when a key is disabled due to an error or validation failure.

ReplicatedRegistry.callbacks.on_key_disabled = function(register_key, reason)
    warn(`Key {register_key} was disabled: {reason}`)
    
    -- Clean up or notify admins
    notifyAdmins(register_key, reason)
end

Server Callbacks

validate_full_request

Validates whether a client can request the full table.

ReplicatedRegistry.server.callbacks.validate_full_request = function(player, key)
    -- Check if player owns this data
    if type(key) == "number" then
        return player.UserId == key
    end
    
    -- Check if key contains player's UserId
    if type(key) == "string" then
        return string.find(key, tostring(player.UserId)) ~= nil
    end
    
    return true
end

Default behavior:

The default callback includes rate limiting and UserId validation:

-- Default implementation
on_request_with_validate_key = function(plr, key)
    -- Rate limit: 5 requests per second
    if not replication_filters.rate_limit(5)(plr) then 
        return false 
    end
    
    -- Wait up to 5 seconds for key to exist
    for i = 1, 5 do
        if registrees[key] then break end
        task.wait(1)
    end
    
    -- Validate ownership
    if type(key) == "number" then
        return plr.UserId == key
    end
    if type(key) == "string" then
        return string.find(key, tostring(plr.UserId)) ~= nil
    end
    
    return false
end

Custom Default Callbacks

You can restore default callbacks if needed:

-- Use default callbacks
ReplicatedRegistry.server.callbacks.validate_full_request = 
    ReplicatedRegistry.default_callbacks.on_request

ReplicatedRegistry.callbacks.on_changes_recieved = 
    ReplicatedRegistry.default_callbacks.on_changes_recieved

Types Reference

Type definitions for ReplicatedRegistry2.

Core Types

Table

export type Table = {[any]: any}

A generic table type used for registree data.

TableChanges

export type TableChanges = {
    {v: any, p: {any}}
}

An array of changes where:

  • v: The new value
  • p: The path to the value as an array

Example:

{
    {v = 100, p = {"coins"}},
    {v = 5, p = {"level"}},
    {v = true, p = {"inventory", "sword"}}
}

Filter Types

Filter

export type Filter = (sender: Player?, register_key: any, tbl: Table, changes: TableChanges) -> boolean

A function that validates changes.

Parameters:

  • sender: The player who sent the change (nil if from server)
  • register_key: The register key being sent changes for.
  • tbl: The old table.
  • changes: Changes waiting to be applied.

Returns: true to accept, false to reject

FilterList

type FilterList = {
    player_blacklist: {number}?,
    player_whitelist: {number}?,
    rate_limit: number?,
    no_recieve: boolean?,
    custom: Filter?,
}

Configuration object for get_filter().

Interface Types

RegistreeInterface

export type RegistreeInterface<T=Table, ReplicateArgs...=()> = {
    set: (...any) -> (any) -> (),
    get: (...any) -> any,
    incr: (...any) -> (number) -> (),
    replicate: (ReplicateArgs...) -> (),
    full: () -> T,
}

The proxy interface returned by view_as_proxy().

Methods:

  • set(...path)(value): Set a value at path
  • get(...path): Get a value at path
  • incr(...path)(amount): Increment a number at path
  • replicate(args...): Replicate changes
  • full(): Get the full table

Server ReplicateArgs: {Player}?, RemoteEvent? Client ReplicateArgs: RemoteEvent?

Internal Types

Registree

type Registree<T=Table> = {
    value: T,
    copy: Table,
    filter: Filter?,
    onRecievedListeners: {(plr: Player?, tbl: Table, changes: TableChanges) -> ()},
}

Internal structure for registered tables.

Sender_Server

type Sender_Server = (plr: Player, register_key: any, changes: TableChanges) -> ()

Function signature for sending changes to a client.

Sender_Client

type Sender_Client = (register_key: any, changes: TableChanges) -> ()

Function signature for sending changes to the server.

RequestFull

type RequestFull = (register_key: any) -> TableChanges?

Function signature for requesting full table from server.

Callback Types

on_changes_recieved

(sender: Player?, register_key: any, changes: TableChanges) -> 
    ("pass" | "return" | "disable", string?)

Returns:

  • "pass": Continue normal processing
  • "return": Stop processing without applying changes
  • "disable": Disable this key entirely
  • Optional second return: reason string (for "disable")

validate_full_request

(player: Player, key: any) -> boolean

Returns: true to allow request, false to deny

on_key_disabled

(register_key: any, reason: string) -> ()

Called when a key is disabled.

Usage Examples

-- Filter function
local myFilter: Filter = function(sender, path, value)
    if path[1] == "coins" and type(value) ~= "number" then
        return false
    end
    return true
end

-- Server proxy
local serverProxy: RegistreeInterface<any, ({Player}?, RemoteEvent?)> = 
    ReplicatedRegistry.server.view_as_proxy("key")

serverProxy.set("coins")(100)
serverProxy.replicate({player})

-- Client proxy
local clientProxy: RegistreeInterface<any, (RemoteEvent?)> = 
    ReplicatedRegistry.client.view_as_proxy("key")

clientProxy.incr("level")(1)
clientProxy.replicate()

-- Changes
local changes: TableChanges = ReplicatedRegistry.get_changes("key")
for _, change in changes do
    print(`Path: {table.concat(change.p, ".")}`)
    print(`Value: {change.v}`)
end

Example: Player Data

A complete example of synchronizing player data between server and client.

Server Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)
local server = ReplicatedRegistry.server

-- Setup remotes
server.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Custom validation
server.callbacks.validate_full_request = function(player, key)
    -- Only allow players to access their own data
    return key == player.UserId
end

local playerDataFilter = ReplicatedRegistry.get_filter({
    rate_limit = 10, -- Max 10 updates per second
    custom = function(sender, register_key, tbl, changes)
    -- Player can only access their own keys
        if sender.UserId ~= register_key then
            return false
        end
        
        for _, c in changes do
            local value, path = c.v, c.p
            -- Prevent negative coins
            if path[1] == "coins" and type(value) == "number" and value < 0 then
                return false
            end
        end
        
        return true
    end
})

Players.PlayerAdded:Connect(function(player)
    local key = player.UserId
    
    -- Create player data
    local playerData = {
        coins = 100,
        level = 1,
        inventory = {},
        settings = {
            sound = true,
            music = true
        }
    }
    
    -- Register for replication
    server.register(key, playerData, playerDataFilter)
    
    -- Listen for changes from client
    ReplicatedRegistry.on_recieve(key, function(sender, tbl, changes)
        print(`Player {sender.Name} updated their data:`)
        for _, change in changes do
            print(`  {table.concat(change.p, ".")} = {change.v}`)
        end
        
        savePlayerData(sender, tbl)
    end)
end)

Players.PlayerRemoving:Connect(function(player)
    local key = player.UserId
    local data = server.view(key)
    
    -- Save before player leaves
    savePlayerData(player, data)
end)

-- Example: Give coins to player
function giveCoins(player, amount)
    local proxy = server.view_as_proxy(player.UserId)
    
    proxy.incr({"coins"}, amount)
    proxy.replicate({player}) -- Send to client
end

-- Example: Update player level
function levelUp(player)
    local data = server.view(player.UserId)
    
    data.level += 1
    data.coins += data.level * 10 -- Bonus coins
    
    server.to_clients(player.UserId, {player})
end

Client Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)
local client = ReplicatedRegistry.client

-- Setup remotes
client.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

local player = Players.LocalPlayer
local key = player.UserId

-- Access player data
local playerData = client.view(key)

-- Listen for updates from server
ReplicatedRegistry.on_recieve(key, function(sender, tbl, changes)
    print("Received update from server:")
    for _, change in changes do
        print(`  {table.concat(change.p, ".")} = {change.v}`)
    end
    
    -- Update UI
    updateCoinsDisplay(tbl.coins)
    updateLevelDisplay(tbl.level)
end)

-- Example: Update settings
function updateSettings(soundEnabled, musicEnabled)
    local proxy = client.view_as_proxy(key)
    
    proxy.set({"settings", "sound"}, soundEnabled)
    proxy.set({"settings", "music"}, musicEnabled)
    
    proxy.replicate() -- Send to server
end

-- Example: Purchase item
function purchaseItem(itemName, cost)
    local data = client.view(key)
    
    if data.coins >= cost then
        data.coins -= cost
        data.inventory[itemName] = true
        
        -- Send to server for validation
        client.to_server(key)
    else
        warn("Not enough coins!")
    end
end

Example: ProfileStore Integration

Integrating ReplicatedRegistry2 with ProfileService for persistent player data.

Server Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local Players = game:GetService("Players")

local ProfileService = require(ServerScriptService.ProfileService)
local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)
local server = ReplicatedRegistry.server

-- Setup remotes
ReplicatedRegistry.server.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- ProfileStore setup
local ProfileStore = ProfileService.GetProfileStore(
    "PlayerData",
    {
        coins = 0,
        level = 1,
        inventory = {},
        stats = {
            kills = 0,
            deaths = 0,
            playtime = 0
        }
    }
)

local Profiles = {}

-- Filter for player profiles
local profileFilter = ReplicatedRegistry.get_filter({
    rate_limit = 10,
    custom = function(sender, register_key, tbl, changes)
        -- Ensure player only modifies their own profile
        return register_key == sender.UserId
    end
})

Players.PlayerAdded:Connect(function(player)
    local profile = ProfileStore:LoadProfileAsync(`Player_{player.UserId}`)
    
    if not profile then
        player:Kick("Failed to load data")
        return
    end
    
    profile:AddUserId(player.UserId)
    profile:Reconcile()
    
    profile:ListenToRelease(function()
        Profiles[player] = nil
        server.deregister(player.UserId)
        player:Kick("Profile released")
    end)
    
    if not player:IsDescendantOf(Players) then
        profile:Release()
        return
    end
    
    Profiles[player] = profile
    
    -- Register profile data with ReplicatedRegistry
    local key = player.UserId
    server.register(key, profile.Data, profileFilter)
    
    -- Listen for changes from client
    ReplicatedRegistry.on_recieve(key, function(sender, tbl, changes)
        print(`{sender.Name} updated profile:`)
        for _, change in changes do
            print(`  {table.concat(change.p, ".")} = {change.v}`)
        end
    end)
    
    -- Periodic sync to client (every 10 seconds)
    task.spawn(function()
        while Profiles[player] do
            task.wait(10)
            if Profiles[player] then
                server.to_clients(key, {player})
            end
        end
    end)
end)

Players.PlayerRemoving:Connect(function(player)
    local profile = Profiles[player]
    if profile then
        profile:Release()
        Profiles[player] = nil
        server.deregister(player.UserId)
    end
end)

-- Example: Give rewards
function giveReward(player, coins, xp)
    local profile = Profiles[player]
    if not profile then return end
    
    local proxy = ReplicatedRegistry.server.view_as_proxy(player.UserId)
    
    proxy.incr({"coins"}, coins)
    proxy.incr({"level"}, xp)
    
    -- Replicate to player
    proxy.replicate({player})
end

-- Example: Add item to inventory
function giveItem(player, itemName)
    local profile = Profiles[player]
    if not profile then return end
    
    local data = ReplicatedRegistry.server.view(player.UserId)
    data.inventory[itemName] = true
    
    ReplicatedRegistry.server.to_clients(player.UserId, {player})
end

-- Example: Update stats
function recordKill(player)
    local profile = Profiles[player]
    if not profile then return end
    
    local proxy = ReplicatedRegistry.server.view_as_proxy(player.UserId)
    proxy.incr({"stats", "kills"}, 1)
    proxy.replicate({player})
end

Client Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

-- Setup remotes
ReplicatedRegistry.client.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

local player = Players.LocalPlayer
local key = player.UserId

-- Wait for data to be ready
local playerData = ReplicatedRegistry.client.view(key)

-- Listen for profile updates
ReplicatedRegistry.on_recieve(key, function(sender, tbl, changes)
    for _, change in changes do
        local path = table.concat(change.p, ".")
        print(`Profile updated: {path} = {change.v}`)
        
        -- Update UI based on what changed
        if change.p[1] == "coins" then
            updateCoinsUI(tbl.coins)
        elseif change.p[1] == "level" then
            updateLevelUI(tbl.level)
        elseif change.p[1] == "stats" then
            updateStatsUI(tbl.stats)
        end
    end
end)

-- Initial UI setup
updateCoinsUI(playerData.coins)
updateLevelUI(playerData.level)
updateStatsUI(playerData.stats)

-- Example: Purchase system
function tryPurchase(itemName, cost)
    local data = ReplicatedRegistry.client.view(key)
    
    if data.coins >= cost then
        -- Optimistic update
        data.coins -= cost
        data.inventory[itemName] = true
        
        -- Server will validate
        ReplicatedRegistry.client.to_server(key)
        return true
    end
    
    return false
end

Notes

  • ProfileStore handles persistence
  • ReplicatedRegistry handles real-time sync
  • Server validates all client changes
  • Client gets immediate feedback with optimistic updates
  • Profile data automatically reconciles on load

Example: Shared Data

Examples of using ReplicatedRegistry2 for shared game state like leaderboards and settings.

Global Leaderboard

Server

local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

-- Setup remotes
ReplicatedRegistry.server.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Create global leaderboard
local leaderboard = {
    topPlayers = {},
    lastUpdate = os.time()
}

-- No filter = read-only for clients
ReplicatedRegistry.server.register("global_leaderboard", leaderboard)

-- Update leaderboard
function updateLeaderboard()
    local proxy = ReplicatedRegistry.server.view_as_proxy("global_leaderboard")
    
    -- Sort players by score
    local players = {}
    for _, player in Players:GetPlayers() do
        table.insert(players, {
            name = player.Name,
            userId = player.UserId,
            score = getPlayerScore(player)
        })
    end
    
    table.sort(players, function(a, b)
        return a.score > b.score
    end)
    
    -- Take top 10
    local top10 = {}
    for i = 1, math.min(10, #players) do
        top10[i] = players[i]
    end
    
    proxy.set({"topPlayers"}, top10)
    proxy.set({"lastUpdate"}, os.time())
    
    -- Broadcast to all players
    proxy.replicate()
end

-- Update every 30 seconds
task.spawn(function()
    while true do
        task.wait(30)
        updateLeaderboard()
    end
end)

Client

local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

ReplicatedRegistry.client.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Get leaderboard
local leaderboard = ReplicatedRegistry.client.view("global_leaderboard")

-- Listen for updates
ReplicatedRegistry.on_recieve("global_leaderboard", function(sender, tbl, changes)
    print("Leaderboard updated!")
    updateLeaderboardUI(tbl.topPlayers)
end)

-- Initial display
updateLeaderboardUI(leaderboard.topPlayers)

Game Settings

Server

local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

-- Setup remotes
ReplicatedRegistry.server.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Game settings
local gameSettings = {
    maxPlayers = 16,
    roundTime = 300,
    gameMode = "FFA",
    mapName = "Arena1",
    friendly_fire = false
}

-- Only admins can modify
local adminFilter = ReplicatedRegistry.get_filter({
    player_whitelist = {123456, 789012}, -- Admin user IDs
    rate_limit = 5
})

ReplicatedRegistry.server.register("game_settings", gameSettings, adminFilter)

-- Listen for admin changes
ReplicatedRegistry.on_recieve("game_settings", function(sender, tbl, changes)
    print(`Admin {sender.Name} changed settings:`)
    for _, change in changes do
        print(`  {table.concat(change.p, ".")} = {change.v}`)
    end
    
    -- Broadcast to all players
    ReplicatedRegistry.server.to_clients(
        "game_settings",
        Players:GetPlayers()
    )
end)

-- Helper function to update settings
function updateGameSettings(newSettings)
    local proxy = ReplicatedRegistry.server.view_as_proxy("game_settings")
    
    for key, value in newSettings do
        proxy.set({key}, value)
    end
    
    -- Broadcast to everyone
    proxy.replicate()
end

Client

local ReplicatedRegistry = require(ReplicatedStorage.ReplicatedRegistry2)

ReplicatedRegistry.client.set_remote_instances(
    ReplicatedStorage.RequestFullRemote,
    ReplicatedStorage.SendChangesRemote
)

-- Get settings
local settings = ReplicatedRegistry.client.view("game_settings")

-- Listen for changes
ReplicatedRegistry.on_recieve("game_settings", function(sender, tbl, changes)
    print("Game settings updated:")
    for _, change in changes do
        local setting = change.p[1]
        local value = change.v
        print(`  {setting} = {value}`)
        
        -- Update UI or game logic
        if setting == "roundTime" then
            updateTimerUI(value)
        elseif setting == "gameMode" then
            updateGameMode(value)
        end
    end
end)

-- Admin panel (only works if player is whitelisted)
function isAdmin(player)
    -- Check if player is admin
    return table.find({123456, 789012}, player.UserId) ~= nil
end

if isAdmin(Players.LocalPlayer) then
    -- Admin can change settings
    function changeRoundTime(newTime)
        local proxy = ReplicatedRegistry.client.view_as_proxy("game_settings")
        proxy.set({"roundTime"}, newTime)
        proxy.replicate()
    end
end

Team Data

Server

local teams = {
    red = {
        score = 0,
        players = 0,
        color = Color3.fromRGB(255, 0, 0)
    },
    blue = {
        score = 0,
        players = 0,
        color = Color3.fromRGB(0, 0, 255)
    }
}

-- Allow server updates only
local filter = ReplicatedRegistry.get_filter({
    no_recieve = true -- No client updates
})

ReplicatedRegistry.server.register("team_data", teams, filter)

-- Update team scores
function addTeamScore(teamName, points)
    local proxy = ReplicatedRegistry.server.view_as_proxy("team_data")
    
    proxy.incr({teamName, "score"}, points)
    proxy.replicate() -- Broadcast to all
end

-- Update player counts
Players.PlayerAdded:Connect(function(player)
    local team = getPlayerTeam(player)
    local proxy = ReplicatedRegistry.server.view_as_proxy("team_data")
    
    proxy.incr({team, "players"}, 1)
    proxy.replicate()
end)

Client

local teams = ReplicatedRegistry.client.view("team_data")

-- Update UI when teams change
ReplicatedRegistry.on_recieve("team_data", function(sender, tbl, changes)
    updateTeamScoreUI(tbl.red.score, tbl.blue.score)
    updateTeamCountUI(tbl.red.players, tbl.blue.players)
end)