Profiles & Observers
Who sees what: one entity observer per profile, answered from geometry every tick, plus an optional tier of counts.
Status: ā Implemented Ā· Visibility: Public Ā· Level: šµ Core Ā· Category: Subscriptions
šÆ What it solves
Every client wants a different slice of the world ā the player the area around it, a god camera what its lens frames, a tool everything ā and the slice moves every tick. Profiles declare the shapes once; the engine keeps each session's slice exact without a per-session list of what it holds.
āļø How it works (in brief)
- One entity observer per profile:
World(),Sphere(r, leave:, max:)orClientRegion(maxEdgeM), over the archetypes named with.Of<T>(). Beside it, at most oneAggregate(tileM, rateHz, radiusM): per-tile, per-archetype counts inAGGblocks, refreshed at a bounded rate, never entity records. - Held = geometry. A Sphere session holds an entity when the entity's visibility position is within the radius of the session's anchor and its replication cell has been delivered; nothing is stored per (session, entity). The anchor moves only when the viewpoint drifts past a small slack, so a still session costs nothing.
- Delivery. A new view is filled cell by cell, nearest first, under
EnterBudgetPerFrame;VIEW_COMPLETEis flagged when it is full. A World session is filled behind one cursor in grid order. A jump of more than a cell is a teleport and resets the view. - Centre of a Sphere:
Place(session, point)from a system every tick; or.At(point),.Bind(entity),.AroundControlled()ā the bound or controlled entity's post-fence position, with no per-tick call. - ClientRegion: the client sends a convex footprint (3ā16 vertices on a flat world, 4ā16 in 3D) through the built-in
ClientRegioncommand (⤠5 Hz); the engine takes the hull, clamps it tomaxEdgeM, and holds the entities inside it;.Near(budget)caps the entities held, whole cells nearest the centroid first, with a deadband. - Refinements: a leave radius (hysteresis ā an entity pacing on the boundary never flaps); a run-time range
(
max:+ctx.Subscriptions.SetRadius, no reset); distance bands (.Bands(b => b.Every(2, beyond: 0.5).Every(4, beyond: 0.75)): far updates every N ticks); cadence (.Every(2 | 4): serve the profile one tick in N, replaying missed ticks).
š» Usage
subs.Profile("player", p => p
.Sphere(192, leave: 208, max: 1500)
.AroundControlled()
.Bands(b => b.Every(2, beyond: 0.5))
.Of<Player>().Of<Creature>().Of<CityNpc>());
subs.Profile("god", p =>
{
p.ClientRegion(maxEdgeM: 3000).Near(10_000).Of<Creature>().Of<Player>();
p.Aggregate(tileM: 256, rateHz: 1).Of<Creature>().Of<Player>();
});
subs.Profile("tool", p => p.World().Of<Creature>().Of<Player>());
| Option | Default | Effect |
|---|---|---|
SubscriptionsOptions.ReplicationCellM |
required | The replication cell side; a session's window is 2āR/cā + 5 cells per axis. Start with ā R/3 (an 11 Ć 11 window) |
SubscriptionsOptions.EnterBudgetPerFrame |
500 | Enter records per frame |
ProfileBuilder.Every(n) |
1 | Serve one tick in 1, 2 or 4 |
ProfileBuilder.Detection(ā¦) |
Explicit |
See Push replication |
ā ļø Guarantees & limits
- A session holds exactly what its geometry names; one never placed holds nothing ā not the area around the origin (SUB-16).
- Worlds are flat or volumetric: a spatial grid one cell deep is served in the plane; a deeper one in 3D (2D archetypes then live on z = 0).
- A Sphere window is bounded (at most 15 cells per axis flat, 13 in 3D; a
ClientRegionwindow at most 2 809 cells);Startrefuses a profile past it, naming the cell side that would fit. - Refused at
Start: two entity observers in one profile, anAggregatealone, a near budget on anything but aClientRegion, a Sphere centred two ways,Far(ā¦)(useAggregate).
š§Ŗ Tests
- SphereObserverTests ā a session holds exactly its disc
- PushRegionTests Ā· PushAggregateTests Ā· PushLodLevelTests Ā· PushOracle3DTests
š Related
- Concept: Replication profile
- Internals: the cell algorithm, and why it scales
- Sessions & admission ā binding a session to a profile