Replicators
Every feature in the kit is split in two: a component that holds the logic and knows nothing about networking, and an adapter beside it that carries that logic across the wire. Offline the adapter never wakes up, so a single-player game pays nothing — and the gameplay code is identical in both.
The split
Take stats. CharacterStats holds values, computes maximums, raises change events and applies effects. It imports no networking code and behaves identically in every game mode. StatReplicator sits next to it and does nothing else but transport.
CharacterStats pure logic, identical in all game modes
StatReplicator transport, only active online
CharacterInventory pure logic
InventoryReplicator transport
RoundController pure logic
RoundReplicator transportThe same shape repeats twelve times. This is not a coding convention — it is what makes the offline claim true. In single-player and local co-op the replicator component is present on the object, but its network spawn never fires, so the logic component's hooks stay null and every operation applies immediately with zero networking overhead. The replicators are not disabled; they are simply never woken.
Four authority flavors
Replicators are not interchangeable. Each one answers a different question about who is allowed to decide, and the answer is the design, not an implementation detail. The kit uses four:
| Field | Type | What it does |
|---|---|---|
Owner-authoritative | flavor | The machine that owns the object holds the truth and mirrors it out. Other machines route their requests to the owner. Used for a player's own stats and inventory — nobody else's business, and nothing to argue about. |
Server-authoritative | flavor | The server holds a world fact and writes it; clients request changes. Used for things that belong to the world rather than to a player: a gate's open state, the map seed, the round. |
Server-arbitrated | flavor | Any machine may detect something; only the server judges it. Used for combat claims and for placing objects — a client says 'I hit them' or 'I want to build here', and the server decides. |
Server-to-owner delivery | flavor | The server has decided something that only the owning machine can carry out. Used for knockback and effect-on-hit payloads: the server judged the hit, but applying it is an owner-only operation. |
The twelve that ship
Every replicator states its flavor in its own description, which is what the dashboard reads. This is the full set. (Some say "host-authoritative" where others say "server-authoritative" — in this kit the host is the server; there is no dedicated-server mode to distinguish them.)
| Field | Type | What it does |
|---|---|---|
StatReplicator | CharacterStats | Owner-authoritative: the owner writes its replicate-flagged stats and every peer mirrors them; non-owners route stat modifications to the owner. |
InventoryReplicator | CharacterInventory | Owner-authoritative: the owner writes its hotbar slots, selection and worn equipment. Also announces world-pickup consumes and drops to every machine. |
CharacterAppearanceReplicator | CharacterCustomizer | Owner-authoritative: the owner writes its chosen look (gender plus every part category) and every peer applies it to their own character, including late joiners. |
RoundReplicator | RoundController | Host-authoritative: round phase, timing (as server time) and results mirror from the host to every machine; owner fail reports travel back up. |
RoundSeriesReplicator | RoundSeries | Host-authoritative: the series' round count, whether it has ended, and every participant's points and wins mirror from the host to every machine, surviving the level reload between rounds. |
MovableReplicator | MovableObject | Server-authoritative: a movable's open/closed state is a world fact, held in one server-write variable that late joiners receive at spawn. |
MapReplicator | MapBuilder | Server-authoritative: replicates only the generator's seed, size and type. Every machine, late joiners included, rebuilds the identical map deterministically. |
CombatReplicator | CombatArbiter | Server-arbitrated: any machine may detect a hit, only the server judges it. Carries a client's damage claim to the server's arbiter. |
CombatPayloadReplicator | CharacterDamageable | Server-to-owner delivery: carries knockback and effect-on-hit payloads from the arbiting server to the machine that owns this character. |
ThrownReplicator | ThrownDamageSource | Server-authoritative flight: routes a client's launch impulse to the server, where the throwable's physics actually runs; transform sync mirrors the flight. |
PlacementReplicator | WorldPlacementSystem | Server-arbitrated request and grant: the server validates, appends to a ledger that late joiners replay, and every machine builds its own copy off the spawn broadcast. |
WorldStateReplicator | (standalone) | Server-authoritative late-join ledgers: which scene pickups are gone and which runtime drops exist, replayed by every joiner. Lives on the scene's World State object — the one replicator with no paired component. |
The pairing is machine-readable
A replicator declares which component it transports, and the editor reads that rather than consulting a hard-coded list:
namespace Curitor.PartyCoreKit.Networking
{
public interface IReplicator
{
// The pure-logic sibling this replicator transports --
// CharacterStats for StatReplicator, and so on. Null for a
// standalone replicator with no paired component.
Type PairedFeature { get; }
}
}
// So StatReplicator says:
public Type PairedFeature => typeof(CharacterStats);That one property is what makes the tooling work. The dashboard builds its rows from it, feature inspectors use it to notice they are missing their partner, and the installer manifest uses it to know what to place. Add a replicator and the editor surfaces pick it up; none of them need editing.
Why they are hidden from Add Component
Every replicator carries [AddComponentMenu("")] — an explicit instruction to keep it out of the Add Component search. The comment on each one says the same thing: installed by Setup or the dashboard; never added by hand.
The reason is that a replicator alone is not a working configuration. It needs its paired component, it needs to be on an object with a network identity, and in the case of the player it needs to be part of the prefab rather than an override on one scene instance. The two paths that do all of that correctly are the installer and the dashboard's Add button, so those are the only two doors.
If a replicator is missing, the fix is one click in the dashboard — not a manual add. See Networking Dashboard.
Writing your own
The seam is open, and the eleven shipped replicators are the reference implementations — reading one is the fastest way to write another. The shape:
| Field | Type | What it does |
|---|---|---|
1. Keep the logic pure | step | Your feature component holds the state and the rules, and imports nothing from the networking layer. It must work standing alone, because offline that is exactly what it does. |
2. Expose a routing hook | step | A property the replicator can set — the way CharacterStats has a modify router. Null means 'apply directly', which is the offline path and needs no branch anywhere else. |
3. Expose an apply-direct path | step | A method the replicator calls to write mirrored state in without re-routing it. This is what a receiving machine uses, and it should still raise the normal change events so UI refreshes. |
4. Implement IReplicator | step | Return typeof(YourFeature) from PairedFeature, and hide the class with AddComponentMenu("") so it can only arrive through the installer or the dashboard. |
5. Pick a flavor deliberately | step | Owner, server, arbitrated, or server-to-owner. Write it in the class description — that text is what the dashboard shows a buyer, and it is the answer to 'who decides?'. |