Skip to content

Accessor Roles

Every WorldAccessor carries an AccessorRole that controls which components it can read/write, whether it can make structural changes, whether it can allocate heap, and which RNG stream it can pull from. The role is set at creation and never changes.

System-owned accessors derive their role from their SystemPhase (see System-owned accessors below). Non-system code creates accessors via world.CreateAccessor(AccessorRole) and picks the role explicitly.

Every rule below is asserted at the call site. Crossing a role boundary throws an immediate TrecsException rather than producing silent desync later.

The three roles

  • Fixed — owns the deterministic simulation. Reads and writes simulation state and allocates persistent heap. Render-only state ([VariableUpdateOnly]) is off-limits. Default for [ExecuteIn(SystemPhase.Fixed)] systems — which is the implicit default for any ISystem.
  • Variable — drives presentation. Reads simulation state and reads/writes the [VariableUpdateOnly] render state. Cannot mutate simulation state or the heap. Default for the three presentation phases (EarlyPresentation, Presentation, LatePresentation) and for input systems (input systems get extra permissions — see Input System).
  • Unrestricted — escape hatch for non-system code (lifecycle hooks, event callbacks, networking, debug tooling, scripting bridges). Bypasses all role rules.

Capability matrix

Capability Fixed Variable Unrestricted
Read sim component (non-[VariableUpdateOnly])
Write sim component (non-[VariableUpdateOnly])
Read [VariableUpdateOnly] component
Write [VariableUpdateOnly] component
Heap mutation (Alloc, Write, Set, Clone, Acquire, Dispose, EnsureCapacity)
Structural change (AddEntity / RemoveEntity / SetTag / UnsetTag) on a non-VUO template
Structural change on a [VariableUpdateOnly] template
Read set (Set<T>().ReadContains, Count, iterate)
Mutate set (Set<T>().DeferredAdd / DeferredRemove / DeferredClear, Set<T>().Write)
SetSystemPaused
FixedRng
VariableRng

VUO field vs VUO template

[VariableUpdateOnly] applies at two scopes that behave differently. The distinction matters for the structural-change rows above.

  • [VariableUpdateOnly] on a component field — the component is render-only state. Fixed cannot read or write it; Variable / input / Unrestricted can. The structural-change rule is unaffected — entities of the parent template are still simulation state, so Fixed and Unrestricted create / remove / partition-transition them.

  • [VariableUpdateOnly] on a template class — the entire template is render-cadence state (cameras, view-only helpers). The structural-change rule inverts: Fixed is rejected; Variable / input / Unrestricted create / remove / partition-transition them. These groups are skipped from the determinism checksum.

See the Components attribute reference for field-level usage.

Picking a role for a standalone accessor

Non-system code (services, scene initializers, editor inspectors, tests) creates its own accessor via World.CreateAccessor(AccessorRole role) (called on the Trecs World instance). Pick the role that matches the work:

  • Fixed — Service classes for the deterministic simulation. For example, a stats service that subscribes to OnAdded / OnRemoved for a tag and bumps a global score component — see Sample 15 — Reactive Events.
  • Variable — UI, camera controllers, rendering services that read both sim and render state.
  • Unrestricted — Scene initialization, debug menus, editor tooling.

System-owned accessors vs standalone accessors

System-owned accessors map their SystemPhase to a role automatically:

SystemPhase AccessorRole
Fixed Fixed
Input / EarlyPresentation / Presentation / LatePresentation Variable

The presentation and input phases collapse into the single Variable role because they share the same access rules — only their execution-order positions differ.

System code never calls World.CreateAccessor(...) — it gets the role from its [ExecuteIn(...)] attribute. Use CreateAccessor only for the standalone cases listed above.

  • Shared Heap Data — the heap-specific subset of these rules, plus deterministic ID minting and the seeder / provider patterns for shared blobs.
  • Input System — how [Input] components and AddInput<T> work with input systems.
  • Time & RNGFixedRng vs VariableRng deterministic streams.
  • Pausing & Disabling Systems — when to use SetSystemPaused vs SetSystemEnabled.