Table of Contents

Class SubscriptionsCommands

Namespace
Typhon.Engine
Assembly
Typhon.Engine.dll

What an application system reads commands through, and answers them with.

public sealed class SubscriptionsCommands
Inheritance
SubscriptionsCommands
Inherited Members

Remarks

Everything here describes exactly one tick. The Engine-Pre drain fills the buffers before the application's track runs, so a command is visible for the tick it was drained into and for no other — never zero ticks, never two (SUB-08). A system that wants a command to outlive its tick copies it into state, which is what state is for.

Semantic validation belongs to the caller. What reaches here has passed the wire's own checks, the declaration's role list, its rate limit and its stateless pre-check. Range against the current world, cooldowns and ownership are the system's, because the state they must agree with is the system's (01-model § 7).

Fields

SleepingClusters

Blocks the projection pass read, and blocks it declined because their cluster was dormant, since start.

public int SleepingClusters

Field Value

int

Remarks

The second number is zero unless the application enabled cluster dormancy, which is what makes the pair readable: a projection cost that did not move means one thing if nothing was declined and the opposite if most of it was.

Properties

AabbChurn

Clusters whose bounds moved per tick, against the active clusters there were — slice 5's gating quantity.

public (long Moved, long Active, long Ticks) AabbChurn { get; }

Property Value

(long Entered, long Changed, long Owed)

BroadClustersReached

Distinct clusters every cell's broad phase reached, against the entity candidates it collected from them.

public long BroadClustersReached { get; }

Property Value

long

BroadClustersStructureChanged

Clusters the broad phase reached whose structure changed.

public long BroadClustersStructureChanged { get; }

Property Value

long

BroadSlots

Live slots the broad phase reached, and those whose structure changed this tick.

public (long Reached, long Changed) BroadSlots { get; }

Property Value

(long fileTableOffset, long manifestOffset)

ChangedClusterCensus

The changed-cluster list's census: clusters named, ticks published, ticks that degraded to "all", and the stopwatch ticks spent publishing.

public (long Named, long Ticks, long CoverAll, long StopwatchTicks) ChangedClusterCensus { get; }

Property Value

(long Reset, long Forced, long Incomplete, long Behind)

Remarks

Slice 1 ships a measurement, and this is it. 9.2 % of watched SLOTS change per tick (20 § 3.1); the CLUSTER figure is what decides whether gating the projection pass on it is worth building, and it was unknown. Cumulative, for the reason 18 § 8.4 records.

ClusterContainment

Occupied slots in clusters wholly inside the enter radius, beside those in clusters the disc clips. Cluster-granularity path only.

public (long Interior, long Boundary) ClusterContainment { get; }

Property Value

(long fileTableOffset, long manifestOffset)

ClusterSize

Cluster radius (box half-diagonal), mean and largest, against the grid cell hosting clusters and the interest cell.

public (double MeanRadius, double MaxRadius, double GridCellSide, double InterestCellSide, long Samples) ClusterSize { get; }

Property Value

(double MeanRadius, double MaxRadius, double GridCellSide, double InterestCellSide, long Samples)

EnterFlow

ENTER and LEAVE records published since start, beside the enters the per-frame budget deferred.

public (long Entered, long Left, long Deferred) EnterFlow { get; }

Property Value

(long Entered, long Changed, long Owed)

Remarks

The ratio separates a view still filling from one being re-told what it already knew, and the two want opposite fixes. Enters far above leaves is a queue draining, and it drains faster with a larger budget. Enters and leaves in step, at a rate far above what the world actually spawns and moves, is the same entities crossing the interest boundary and crossing back — there a larger budget spends more wire on the same entities arriving again. Read it beside ViewFill, which says whether the client's world is still growing.

EntityCandidatesCollected

Entity candidates every cell's broad phase collected.

public long EntityCandidatesCollected { get; }

Property Value

long

EntriesMigrated

Replication entries carried from one cluster to another since start, summed over every archetype.

public long EntriesMigrated { get; }

Property Value

long

Remarks

A relocation is invisible to the world and loud on the wire. The entity did not move and nothing about it changed, but it now sits in a different cluster — so a session that watches the source and not the destination is told the entity LEFT, and one that watches the destination and not the source is told it ENTERED. Only a session holding both suppresses the pair. Read against LeaveCauses: if the two rates track, the churn belongs to whatever relocates entities rather than to observers moving.

EpochEnter

Mean microseconds a subscriptions chunk spends entering its epoch, and how many chunks were measured.

public (double MeanUs, long Chunks) EpochEnter { get; }

Property Value

(double MeanUs, long Chunks)

FrameBalance

The frame stage's effective worker count and parallel efficiency; zero unless phase timing is on.

public (double Effective, double Efficiency, long Ticks) FrameBalance { get; }

Property Value

(double Known, double Owed, long Frames)

FramePhases

The frame stage's phases, in ms of CPU summed over workers since start. All zero unless phase timing was enabled.

public (double Gather, double Select, double Sweep, double Sort, double Encode, double Publish) FramePhases { get; }

Property Value

(double Gather, double Select, double Sweep, double Sort, double Encode, double Publish)

FramePrologueMs

The frame stage's single-threaded prologue, per tick, in ms.

public (double Prologue, double Sweep, double Prepare) FramePrologueMs { get; }

Property Value

(double BroadUs, double NarrowUs, double FlushUs)

FrameSpan

The frame stage's span against the busy time inside it, and the concurrency the two imply.

public (double SpanMs, double BusyMs, double Concurrency, double StartSpreadMs) FrameSpan { get; }

Property Value

(double InteriorUs, double RetainUs, double CloseUs, double GroupUs)

FullGatherCauses

Why the incremental path fell back to the full walk: reset, forced, incomplete view, or behind by more than one tick.

public (long Reset, long Forced, long Incomplete, long Behind) FullGatherCauses { get; }

Property Value

(long Reset, long Forced, long Incomplete, long Behind)

GatherRunShape

Interest runs the gather walked, and how many of them had nothing to say.

public (long Walked, long Empty) GatherRunShape { get; }

Property Value

(long fileTableOffset, long manifestOffset)

GatherShape

How many frames since start were built as a difference against the session's previous tick, and how many walked the whole view (15 § 3.2).

public (long Difference, long Full) GatherShape { get; }

Property Value

(long fileTableOffset, long manifestOffset)

Remarks

Cumulative, as InterestSharing now is. It exists for the same reason: a measurement showing no change means "the difference does not pay" or "the difference never happened", and those call for opposite next steps. A ratio near zero is the second.

GatherSlots

Hit slots the difference read, and hit slots it carried forward without reading, since start (15 § 3.2).

public (long Visited, long Carried) GatherSlots { get; }

Property Value

(long fileTableOffset, long manifestOffset)

GroupingDiagnostic

Cluster runs accepted whole against runs the disc clipped, and the slots each accounted for.

public (long Ungroupable, long Keyed, double SpanX, double SpanY, double CellSide) GroupingDiagnostic { get; }

Property Value

(long Ungroupable, long Keyed, double SpanX, double SpanY, double CellSide)

IdentityFlow

Network identities minted, released and reused since start — the denominator for LeaveCauses's stale term.

public (long Minted, long Released, long Reused) IdentityFlow { get; }

Property Value

(long Entered, long Changed, long Owed)

Remarks

Every archetype shares one allocator, so the first non-null state answers for all of them.

InterestBookkeeping

The interest bookkeeping the phases leave out, and the resolved groups' total, in microseconds. Needs MeasureInterestPhases.

public (double InteriorUs, double RetainUs, double CloseUs, double GroupUs) InterestBookkeeping { get; }

Property Value

(double InteriorUs, double RetainUs, double CloseUs, double GroupUs)

InterestCoherence

How much of the interest answer has been what the sessions already held, since start.

public (long RunsUnchanged, long RunsTotal, long SessionsCoherent, long SessionsTotal) InterestCoherence { get; }

Property Value

(long Reset, long Forced, long Incomplete, long Behind)

InterestPhases

Where the interest stage's time has gone since start, in microseconds. Needs MeasureInterestPhases.

public (double BroadUs, double NarrowUs, double FlushUs) InterestPhases { get; }

Property Value

(double BroadUs, double NarrowUs, double FlushUs)

InterestSharing

Last tick: how many interest cells the broad phase resolved, and how many sessions were served from a resolution somebody else paid for.

public (long Cells, long SessionsShared) InterestSharing { get; }

Property Value

(long fileTableOffset, long manifestOffset)

The cells resolved and the sessions shared.

Remarks

A diagnostic, and the one the cell-keyed broad phase is judged by. Its whole benefit is that co-located observers resolve once, so a deployment whose sessions never share a cell pays the grouping's sort and gets nothing back — and no timing comparison can tell that case from a design that does not work, because both look like "no change". Reading it is how an operator, or a measurement, tells them apart.

Cumulative since start, and it was per tick until a zero from it was believed. Sampled after a load generator disconnects, the per-tick form read "0 cells, 0 sessions" and was written up as the grouping failing at the density where it matters most. There were simply no sessions left to group. See the note at the reset site in InterestPass.

InterestSpan

The interest stage's parallel shape per tick: span, summed busy, slowest chunk, start spread, heaviest group and its size, serial prologue.

public (double SpanMs, double BusyMs, double MaxChunkMs, double StartSpreadMs, double HeaviestGroupMs, double HeaviestGroupMembers, double PrologueMs) InterestSpan { get; }

Property Value

(double SpanMs, double BusyMs, double MaxChunkMs, double StartSpreadMs, double HeaviestGroupMs, double HeaviestGroupMembers, double PrologueMs)

InterestStageShape

The interest stage's shape, cumulative since start (design 23, phase 0): ticks folded, wall and summed busy, the heaviest group and how late it started, chunks that did almost nothing, distinct threads that ran chunks, and the snapshot claims that read a cluster's page privately because another worker was filling it. Durations in Stopwatch ticks.

public (long Ticks, long SpanTicks, long BusyTicks, long HeaviestTicks, long HeaviestStartTicks, long PhantomChunks, long Threads, long PrivateReads, long Fills, long Reads, long ActiveClusters, long Prefills, long PrefillTicks) InterestStageShape { get; }

Property Value

(long Ticks, long SpanTicks, long BusyTicks, long HeaviestTicks, long HeaviestStartTicks, long PhantomChunks, long Threads, long PrivateReads, long Fills, long Reads, long ActiveClusters, long Prefills, long PrefillTicks)

InteriorAdmission

Clusters an interest cell admitted whole, and the entity reads that avoided.

public (long Clusters, long EntitiesSkipped) InteriorAdmission { get; }

Property Value

(long fileTableOffset, long manifestOffset)

LeaveCauses

Why the leaves were sent: geometry, a reissued identity, or the known-set sweep — beside what the interest pass merely stopped reaching.

public (long Considered, long Interest, long Stale, long Swept) LeaveCauses { get; }

Property Value

(long Reset, long Forced, long Incomplete, long Behind)

ObserverMotion

How far the OBSERVERS moved: steps compared, total displacement in millimetres, and placed session-ticks seen.

public (long Steps, long Millimetres, long Placed) ObserverMotion { get; }

Property Value

(long Entered, long Changed, long Owed)

Remarks

The quantity every enter and leave rate is a consequence of. An observer advancing d in a tick sweeps about 2Rd of area out of the back of its disc, so at a known density the entities it must drop are arithmetic, and a churn figure read without it is a number with no expected value beside it.

It is the counter that found the SWG demo binding its sessions to an unstable iteration order. The viewpoints were moving 118.8 m per tick against the 0.1 m a player covers at 50 Hz, and every symptom read as an engine fault — an enter-to-leave ratio of 1.05, an enter backlog that never drained, leaves naming entities that had not moved — followed from it. Nothing else in the track reported observer speed, so nothing else could tell a busy world from a teleporting camera.

ObserverStationary

Session-ticks whose viewpoint did not move at all.

public long ObserverStationary { get; }

Property Value

long

OpenSessions

Every session that is open right now, for an application that has to touch all of them — placing their observers, most of it.

public OpenSessionView OpenSessions { get; }

Property Value

OpenSessionView

ProjectPrologueMs

The projection stage's serial blocks step per tick, split into block creation, parked-entry drain and the watched-block gather, and its parallel busy time per tick, in ms. Zero unless phase timing is on.

public (double Create, double Drain, double Gather, double Busy) ProjectPrologueMs { get; }

Property Value

(double InteriorUs, double RetainUs, double CloseUs, double GroupUs)

ProjectionBlocks

Blocks the projection pass read, and blocks it declined because their cluster was dormant, since start.

public (long Projected, long Dormant) ProjectionBlocks { get; }

Property Value

(long fileTableOffset, long manifestOffset)

ProjectionSlots

Slots the projection addressed, and the subset it named as changed, since start.

public (long Addressed, long Changed) ProjectionSlots { get; }

Property Value

(long fileTableOffset, long manifestOffset)

RunFlow

How a session's cluster set turns over: first seen since its last published frame, carried, and departed. Since start.

public (long FirstSeen, long Carried, long Departed) RunFlow { get; }

Property Value

(long Entered, long Changed, long Owed)

RunsRetainedByView

Runs a moving member's view retained in one sequential pass instead of a probe each.

public long RunsRetainedByView { get; }

Property Value

long

SendPath

The send path, measured while phase timing is on — see SendPump.SendPath.

public (double WakeMsPerPublish, double WokenPerPublish, double QueueDelayUs, double SendUs, long SendsSync, long SendsAsync) SendPath { get; }

Property Value

(double WakeMsPerPublish, double WokenPerPublish, double QueueDelayUs, double SendUs, long SendsSync, long SendsAsync)

SendTotals

Frames and bytes handed to links since start.

public (long Frames, long Bytes) SendTotals { get; }

Property Value

(long fileTableOffset, long manifestOffset)

SessionEvents

This tick's session lifecycle events — Opened, Closed — delivered in Engine-Pre, so a system reacts to one with an ordinary transaction in the same tick.

public ReadOnlySpan<SessionEvent> SessionEvents { get; }

Property Value

ReadOnlySpan<SessionEvent>

ShareCensus

Slots the projection named as changed, against the records the frame stage actually emitted — Layer 4's sharing ratio.

public (long ChangedSlots, long Records) ShareCensus { get; }

Property Value

(long fileTableOffset, long manifestOffset)

SharedRunMisses

Why a run with something to say did not reference shared bytes (17 § 18).

public (long Gated, long NoRun, long NotReached) SharedRunMisses { get; }

Property Value

(long Entered, long Changed, long Owed)

SharedRunSkips

Clusters that published a shared cluster run, and why the others did not (17 § 18).

public (long Published, long Released, long Init, long NoChange) SharedRunSkips { get; }

Property Value

(long Reset, long Forced, long Incomplete, long Behind)

SharedRuns

Runs referenced rather than encoded, the records they carried, the clusters that offered one and could not be shared, and the records the projection encoded once (17 § 18).

public (long Runs, long Records, long Refused, long Built) SharedRuns { get; }

Property Value

(long Reset, long Forced, long Incomplete, long Behind)

SnapshotUse

Clusters opened to fill the shared snapshot, against those read from it.

public (long Opens, long Reads) SnapshotUse { get; }

Property Value

(long fileTableOffset, long manifestOffset)

SpanClaims

Span handouts the projected-component mask dropped, against those it let through.

public (long Suppressed, long Admitted) SpanClaims { get; }

Property Value

(long fileTableOffset, long manifestOffset)

SparseTopology

Runs sparse sessions did not emit, and the content runs synthesized for them from the changed-block tables.

public (long Skipped, long Synthetic) SparseTopology { get; }

Property Value

(long fileTableOffset, long manifestOffset)

StaleMask

Retained slots read in full because the block's change mask named another tick, and the runs that caused it.

public (long Slots, long Runs) StaleMask { get; }

Property Value

(long fileTableOffset, long manifestOffset)

TopologyRunsRetained

Runs a session re-emitted from its maintained topology without running the kernel.

public long TopologyRunsRetained { get; }

Property Value

long

ViewFill

Mean entities a client holds, and mean enters still owed to it, over the frames that published.

public (double Known, double Owed, long Frames) ViewFill { get; }

Property Value

(double Known, double Owed, long Frames)

ViewShape

What moving members' views were made of: clusters wholly inside (admitted whole, or tested), partly inside, and those partial ones' entities.

public (long AdmittedWhole, long TestedWhole, long Partial, long PartialInside, long PartialTotal) ViewShape { get; }

Property Value

(long AdmittedWhole, long TestedWhole, long Partial, long PartialInside, long PartialTotal)

VisitParts

What the reduced gather's visited slots were made of: newly entered, named by the change mask, owed by an earlier frame.

public (long Entered, long Changed, long Owed) VisitParts { get; }

Property Value

(long Entered, long Changed, long Owed)

Methods

Commands<T>()

This tick's commands of one type.

public CommandBatch<T> Commands<T>() where T : unmanaged

Returns

CommandBatch<T>

The batch, empty when nothing arrived.

Type Parameters

T

The command's struct, as the application declared it.

Exceptions

InvalidOperationException

T was never declared as a command type.

CopyInterestGroupHistogram(Span<long>)

Copies the interest stage's cumulative log2 histogram of group microseconds (16 buckets) into into.

public void CopyInterestGroupHistogram(Span<long> into)

Parameters

into Span<long>

At least 16 elements.

Place(SessionId, Vector3D)

Places a session's spatial observers for THIS tick.

public bool Place(SessionId session, Vector3D position)

Parameters

session SessionId

The session.

position Vector3D

Where it is looking from, in world space.

Returns

bool

false when the session is closing or gone.

Remarks

It applies now, unlike everything on Session(SessionId, int). A profile, a budget or the entity a session controls are configuration, and the request log stages them so they take effect from a known boundary. A viewpoint is not configuration — it is this tick's position — and a tick of latency on it means every session resolves its interest around where it was rather than where it is.

Call it from an application system, once per session per tick, before the replication track runs. A session with a Sphere observer that has never been placed sees nothing at all: an unplaced session is nowhere, not at the origin, because a default position is a legal world position and silently giving everyone a sphere around it is worse than giving them nothing.

Reject(SessionId, ushort, byte)

Answers a session's command by sequence number.

public bool Reject(SessionId session, ushort seq, byte reasonCode)

Parameters

session SessionId

Whose command it was.

seq ushort

Its sequence number.

reasonCode byte

An AckReasons code.

Returns

bool

false when the tick's rejection log was full.

Reject<T>(in ClientCommand<T>, byte)

Answers a command with a rejection, which reaches the client as an ACK record.

public bool Reject<T>(in ClientCommand<T> command, byte reasonCode) where T : unmanaged

Parameters

command ClientCommand<T>

The command being refused.

reasonCode byte

An AckReasons code; 128-255 are the application's own.

Returns

bool

false when the tick's rejection log was full and the answer was dropped.

Type Parameters

T

The command's struct.

Remarks

A command that is simply not applied sends nothing — the client learns only that its seq was consumed. Rejecting is for telling it WHY, and it costs a record in the next frame, so it is a decision rather than a default.

Session(SessionId, int)

Asks something of a session: the profile it is bound to, the observers it carries, the entity it controls, its budget, or a kick.

public SessionRequest Session(SessionId session, int worker = 0)

Parameters

session SessionId

The session.

worker int

Which worker's segment to stage on: the tick context's worker index inside a chunked parallel system, and 0 on the tick driver or in a serial one.

Returns

SessionRequest

A builder whose calls are recorded and applied by the next tick's prologue.

Remarks

This is how a session gets a profile, and without one it receives nothing: interest is gathered per profile, so a session bound to none is not in any tick's session set. The natural place to call it is the SessionEvents loop, on the Opened event.

Nothing happens immediately. A session row belongs to the tick (SUB-05); the calls are appended to a per-worker segment and applied single-threaded at the start of the next tick, before interest is gathered. That is what makes it safe to call from any system on any worker.

One worker per segment, and an out-of-range index is refused rather than clamped. A segment's append is unsynchronized — that is what makes it free — so two threads sharing one loses records, duplicates them, or throws out of an Array.Resize. Clamping a too-large index onto the last segment would silently arrange exactly that, so it throws instead: a system that passes the wrong index learns immediately rather than corrupting a neighbour's list under load.

Exceptions

ArgumentOutOfRangeException

worker is negative, or at or above the worker count this tick dispatched.

SessionKindOf(SessionId)

The kind a session named in its HELLO, or null when it is gone.

public string SessionKindOf(SessionId session)

Parameters

session SessionId

The session.

Returns

string

The kind.

TryGetLastSeq(SessionId, out ushort)

The highest sequence number drained into a tick for a session, which is what SELF.lastSeq reports.

public bool TryGetLastSeq(SessionId session, out ushort lastSeq)

Parameters

session SessionId

The session.

lastSeq ushort

The sequence number.

Returns

bool

false when that session has never sent a command.

Remarks

Every sequence at or below it was applied, rejected or coalesced away, which is exactly what a client's prediction reconciliation needs (03-wire-protocol § 12 W31).

TryResolve(SessionId, uint, out EntityId)

Resolves an entity reference a client sent — a netId on the wire — back to the entity it names.

public bool TryResolve(SessionId session, uint netId, out EntityId entity)

Parameters

session SessionId

The session that sent the reference.

netId uint

The network identity, as the command carried it.

entity EntityId

The entity.

Returns

bool

false when nothing live holds that identity, or the session is gone.

Remarks

An unknown identity is not an error. A client may name an entity that has since left, or one it was never shown; the answer is "no", and the system decides what that means. Treating it as malformed input would let one stale reference close a connection.

The known-set half of this check is not built yet. 01-model § 7 requires that a client can only target what it was shown, which needs the per-session known-set that P1-13a builds. Today the identity must merely be live and bound, so a client that guesses a valid netId is not refused for it. That gap is stated here rather than hidden: the clause is added where the known-set arrives, and nothing above this line has to change for it.