Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/NeonD00m/feces/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers the simplest working replication setup for feces. It uses plain Roblox RemoteEvents and is suitable for most games that don’t replicate extremely large amounts of data frequently. If you’re just getting started, this is the recommended approach.

Overview

There are three pieces to this setup:
  1. Full snapshot on join — when a client connects, it requests the current world state and receives it all at once.
  2. Delta loop — every frame (or however frequently you choose), the server computes what has changed and sends only those changes to each player.
  3. Client-side apply — the client receives packets and applies them to its local feces instance.

Full Setup

1

Create your RemoteEvents

You will need two RemoteEvent instances in ReplicatedStorage:
  • requestFullPacket — fired by the client to ask for a full world snapshot on join.
  • remote — the general replication event used for both full and delta packets.
2

Server and client wiring

Place this code in a shared module or split it across a server Script and a LocalScript. The RunService:IsServer() guard selects the correct branch at runtime.
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local feces = -- your feces instance
local remote = ReplicatedStorage.remote
local requestFullPacket = ReplicatedStorage.requestFullPacket

if RunService:IsServer() then
    -- When a client joins and fires requestFullPacket,
    -- respond with a full world snapshot just for that player.
    requestFullPacket.OnServerEvent:Connect(function(player)
        remote:FireClient(player, feces.full())
    end)
else
    -- Ask the server for a full snapshot immediately on join.
    requestFullPacket:FireServer()

    -- Apply every incoming packet (full or delta) to the local world.
    remote.OnClientEvent:Connect(function(data)
        feces.apply(data)
    end)
end
3

Server replicate() function

Call replicate() at the end of each server frame to compute and send per-player delta packets.
local function replicate()
    local changes, deleted = feces:delta()

    -- Skip the fire if nothing has changed this frame.
    if not next(changes) and not next(deleted) then
        return
    end

    -- feces.combine() groups the raw changes and deletions into
    -- per-player packets, so each player only receives what is
    -- relevant to them.
    for player, packet in feces.combine(changes, deleted) do
        remote:FireClient(player, packet)
    end
end
4

Hook replicate() into your game loop

Call replicate() from your server-side heartbeat or scheduler so it runs once per frame.
RunService.Heartbeat:Connect(function()
    replicate()
end)
You can call replicate() at any point in your frame — at the end is typical so all ECS systems have already run and all component changes for that frame are captured in a single delta pass.

How Each Part Works

requestFullPacket and feces.full()

When a fresh client connects it has no world state. Firing requestFullPacket to the server triggers feces.full(), which returns a complete snapshot of everything currently marked as replicated. The server fires this snapshot back to just that player via remote:FireClient(player, ...). On the client, the same OnClientEvent handler that processes deltas also processes this initial full snapshot — both call feces.apply(data), so no special-case code is needed.

feces:delta()

feces:delta() returns two tables:
  • changes — a map of components to per-player entity data that has changed since the last call.
  • deleted — a map of players to arrays of entity lookup keys that have been deleted since the last call.
If both are empty, there is nothing to send and the early return prevents an unnecessary network call.

feces.combine(changes, deleted)

feces.combine() merges the raw changes and deleted tables into a single per-player packet map. Iterating over its result gives you (player, packet) pairs, where each packet contains only the data that specific player needs to receive.
Because feces.combine() partitions data by player, players who are not observing a given entity never receive updates for it. This keeps packet sizes small without any extra filtering logic in your code.

feces.apply(data) (client)

On the client, feces.apply(data) takes any packet — whether a full snapshot or a delta — and reconciles it with the local jecs world. It creates new local entities, updates changed component values, removes components, and deletes entities as directed by the packet. Any hooks you have registered will fire during this process.

Build docs developers (and LLMs) love