---
title: "Reference: Netcode Contract"
description: "The emitted RuntimeNetcodeContract fields, input modes, movement specs, replication lanes, policies with their constants, capacity numbers, and the proof surfaces that pin them."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/reference/netcode-contract/
---

# Reference: Netcode Contract

{% version engineVersion="v1.0.232" coordinate="docs-public.v0" /%}

_Last verified 2026-09-02 against engine v1.0.232._

The **netcode contract** is the backend-owned interface a room hands each client at session start. Unlike the component, action, or script references, it has **no single generated file**; its source of truth is the runtime code plus a fixture test that hashes the emitted JSON. This page enumerates the emitted fields, the input modes and movement specs, the replication lanes, the policies with the constants that carry them, the capacity numbers (with the code's own `provisional` qualifier where it applies), and the proof surfaces. For the model behind these values (the clock equations, authority, admission, load), read [the Netcode Model](../explanation/netcode-model.md); this page states the values, that page states the why.

## Source of truth

The contract is resolved by `resolveRuntimeNetcodeContract` in `server/src/modules/runtime/runtimeNetcodeContract.ts`. It takes a room, the authored project graph, and a runtime budget, validates the result against the protocol schema `RuntimeNetcodeContractSchema`, and hashes the canonical JSON with `hashRuntimeNetcodeContract`. The version tag is the constant `RUNTIME_NETCODE_CONTRACT_VERSION` and the wire protocol tag is `RUNTIME_PROTOCOL_VERSION`, both in `packages/engine-version`.

The contract is cached per content by `runtimeNetcodeContractRevisionKey`, which fingerprints exactly the room and content coordinates (`roomId`, `gameId`, `deploymentId`, `contentVersionId`, `environment`, `tickRateHz`, `snapshotRateHz`, `blueprintId`, `roomTemplateKey`, `runtimeBudgetKey`, `runtimeBudget`) and excludes volatile bookkeeping such as player counts, the lease, and tick progress, so identical content always resolves an identical contract.

## Emitted fields

`resolveRuntimeNetcodeContract` emits these top-level fields.

| Field | What it carries |
| --- | --- |
| `version`, `hash` | The contract version tag and the sha256 of the canonical JSON. |
| `tickPolicy` | Clock domain, tick and snapshot rates, time precision. |
| `transportPolicy` | Protocol version, supported encodings, per-stream state-stream limits. |
| `inputPolicy` | The input cadence policy (packet rate, redundancy window, liveness refresh). |
| `inputModes` | The static input-mode contracts (`RUNTIME_NETCODE_INPUT_MODES`). |
| `actionContracts`, `actions` | Per-authored-action resolved mode, prediction policy, and controller. |
| `movementControllerSpecs` | The kinematic and physics movement controller specs. |
| `predictionPolicies` | `prediction.none` and `prediction.replay.shared`. |
| `interpolationPolicies` | The remote interpolation profiles. |
| `presentationPolicies` | The presentation smoothing policy. |
| `replicationManifest` | Lanes, component policies, and bandwidth. |
| `scriptPredictionContracts` | Deterministic predicted-script contracts derived from the graph. |
| `lagCompensationPolicies` | The `live` and `rewind_to_client_sample` policies. |
| `collisionSnapshot` | The authored static-collision snapshot for the shared controller. |
| `diagnostics` | Resolver diagnostics for unsupported or demoted actions. |

## Tick and time policy

`tickPolicy` declares `clock: "server_tick"`, `timePrecision: "nanoseconds"`, and `integerMillisecondStep: false`; the tick and snapshot rates are taken from the room. The default room shape used by the load harness is `RUNTIME_LOAD_TEST_POLICY.defaultTickRateHz` of 20 and `defaultSnapshotRateHz` of 10.

Clock values are branded time domains defined in `packages/runtime-shared/src/time/brandedTime.ts`: `MonotonicMs` (process-relative, cadence only) and `WallMs` (epoch, the only domain written to a durable or wire record). The brands are nominal, so a cross-domain assignment is a compile error, and the single conversion boundary is `wallFromMonotonic` against an explicit `ClockAnchor`. The server module `server/src/modules/runtime/kernel/time.ts` re-exports that hoisted module, and `scripts/check-clock-domains.mjs` bans raw `Date` construction from a number in the cadence and netcode paths.

## Input modes

`RUNTIME_NETCODE_INPUT_MODES` declares the static input-mode contracts the client admission gate enforces.

| Mode | Sends to server | Mutates replicated state | Allowed effect systems | Notes |
| --- | --- | --- | --- | --- |
| `server_authoritative` | yes | yes | movement, action, script | The fail-closed default. |
| `client_predicted` | yes | yes | movement, action, script | Predicted and replayed; falls back to disabling prediction. |
| `client_only` | no | no | look | Presentation-only; local visual state. |
| `anticipated` | yes | no | action, look | Sent for storage but visual-only; gameplay stays server-authoritative. |

Scripted prediction is admitted through `admitPredictedScript` only for a script the graph declares deterministic, whose host functions are on the declared allowlist, and whose ops fall in the narrow predicted-op allowlist; otherwise the script's ops stay server-authoritative and reconcile.

{% warning severity="caution" title="client_only advertises look but the resolver also admits action" %}
The static `client_only` mode carries `allowedEffectSystems: ["look"]`, but the per-declaration resolver `runtimeActionContractForDeclaration` (called for every declared action by `resolveRuntimeNetcodeContract`) admits an effect system of `look` **or** `action` (Presentation Programs consume declared action events in browser memory without mutating replicated state) and demotes any other effect under `client_only` with the `client_only_gameplay_mutation_blocked` diagnostic. Movement and script effects therefore remain forbidden under `client_only`. Treat the runtime rule (look plus action) as authoritative and the mode's `allowedEffectSystems` field as the narrower advertised value; the divergence is real and lives at those two sites.
{% /warning %}

## Movement controller specs

The contract emits two movement controller specs. The kinematic spec `movement.transform.shared_kcc` (`shared_kinematic_character`) supports prediction and is selected when the transform owner is `transform`. The physics spec `movement.physics.authority` (`server_authoritative_physics`) disables prediction and is selected when the transform owner is `physics`; a `client_predicted` movement action on a physics body is demoted to server-authoritative because dynamic-rigidbody prediction would require a rollback physics contract the engine does not ship.

## Prediction and interpolation

`predictionPolicies` emits `prediction.none` (rollback and replay off) and `prediction.replay.shared` (rollback and replay on), both bounded by `DEFAULT_MAX_CATCHUP_STEPS`.

`interpolationPolicies` emits **three** remote profiles, selected per entity via {% component name="NetworkProfileComponent" /%} `NetworkProfileComponent`:

- `interpolation.remote.default`: the standard delay buffer; extrapolation enabled to the cap.
- `interpolation.remote.responsive`: extrapolation disabled (`maxMs: 0`), so it hugs the newest sample and never bridges a gap.
- `interpolation.remote.smooth`: the delay buffer doubled for fluid slow movers, extrapolation enabled to the cap.

The extrapolation cap is `DEFAULT_INTERPOLATION_GAP_BRIDGE_MAX_MS` of 200 ms: a stalled lane bridges at most that long before halting. Presentation smoothing uses `DEFAULT_PRESENTATION_TAU` of 90 ms.

## Lag compensation

The lag-compensation numbers are a default, a clamp band, and a fairness ceiling, and they are easy to conflate. The values below are pinned by `server/tests/runtimeNetcodeContractFixtureHash.test.ts`.

| Value | Constant | Meaning |
| --- | --- | --- |
| 150 ms | `RUNTIME_LAG_COMPENSATION_POLICY.defaultMaxRewindMs` | The rewind window a room **advertises** by default. |
| 100 ms | `RUNTIME_LAG_COMPENSATION_POLICY.minRewindMs` | The clamp floor for a per-blueprint override. |
| 200 ms | `RUNTIME_LAG_COMPENSATION_POLICY.maxRewindMs` | The clamp **ceiling**; not the shipped value. |
| 220 ms | `DEFAULT_FAIRNESS_CEILING` | The fairness ceiling emitted on both policies. |

The emitted `lagCompensationPolicies.live.maxRewindMs` and `lagCompensationPolicies.rewind_to_client_sample.maxRewindMs` are both **150 ms** by default, because `clampLagCompensationMaxRewindMs` computes `min(200, max(100, requested))` over `defaultMaxRewindMs`. The 200 ms figure is only reached when a per-blueprint `lagCompensationMaxRewindMs` override is set above it. Read the emitted value, not the ceiling.

## Replication

State replicates on two unreliable lanes emitted in `replicationManifest.lanes`.

| Lane | Cadence | Audience | Reliable |
| --- | --- | --- | --- |
| `pose` (`RUNTIME_REPLICATION_LANE_POSE`) | tick rate | `public` | false |
| `world` (`RUNTIME_REPLICATION_LANE_WORLD`) | snapshot rate | `interested` | false |

Interest and bandwidth come from `RUNTIME_REPLICATION_POLICY`: `bytesPerSecond` of 256 KiB, `defaultInterestRadius` of 80, `interestCellSize` of 40, and the priority tiers `priorityHigh` 100, `priorityMedium` 50, `priorityLow` 10.

The world delta is fitted to a byte budget by `selectRuntimeWorldDeltaForBudget`, which returns `undefined` (refuses) when the delta carries a `sceneFrameUpdate`, and, when trimming to fit, pulls each required parent upsert in before its children via `sortEntityUpsertsParentBeforeChild`. Which entities a client receives is composed by `composeRuntimeClientSnapshotEntities`, which unions the interested and always-relevant sets and subtracts owner-private entities last.

State streams add their own budgets and anti-cheat guards in `RUNTIME_STATE_STREAM_POLICY` and `runtimeStateStreamPolicyConfig`: `perStreamMaxRateHz` 60 and `perStreamMaxBytesPerSec` 32768, `perPlayerMaxAggregateBytesPerSec` 128000, `perRoomMaxAggregateBytesPerSec` 1048576, sample windows `sampleTimeWindowBehindMs` 500 and `sampleTimeWindowAheadMs` 100, `antiCheatMaxLinearVelocityMps` 50 and `antiCheatMaxAngularVelocityRadps` 25, plus `maxStreamsPerPlayer` 16, `maxSamplesPerStream` 8, and `teleportDistanceMeters` 25.

The three authority fan-out producers (`prepareAuthorityFrame`, `prepareNotice`, `preparePlayerPrivateFrame`) may be referenced only from the authorized corridor files, enforced by `scripts/check-netcode-replication-fanout-boundary.mjs`.

## Load ladders

Overload runs down two ordered ladders in `server/src/modules/runtime/backpressure.ts`.

The per-room ladder `RUNTIME_LOAD_DEGRADATION_ORDER` sheds in order: `downsample_or_drop_state_streams`, `drop_low_priority_replication`, `reject_room_commands`, `evict_slow_consumers`, `mark_room_unhealthy`.

The per-shard ladder `RUNTIME_SHARD_LOAD_DEGRADATION_ORDER` composes above it and sheds in order: `thin_snapshots`, `reduce_snapshot_rate`, `pause_non_essential_lanes`, `refuse_joins`, `migrate_or_close`. `evaluateShardDegradation` returns the single highest rung whose trigger the current signals meet, and the ladder never relaxes the event-loop-lag kill threshold to make room.

Backpressure is driven by the ack-lag thresholds `ACK_LAG_WATCH` 8, `ACK_LAG_THROTTLING` 32, and `ACK_LAG_SHEDDING` 128, and by the socket-byte thresholds in `RUNTIME_SLOW_CONSUMER_POLICY`: `watchBytes` 1 MiB, `throttleBytes` 4 MiB, `evictBytes` 16 MiB.

## Durability and capacity

A room checkpoints on `RUNTIME_DURABLE_CHECKPOINT_INTERVAL_MS` of 30000 ms. The owner lease term is `RUNTIME_ROOM_OWNER_LEASE_TTL_SECONDS` of 30 s. A rejoining player presents a MAC-signed resume token bounded by `RUNTIME_RESUME_TOKEN_TTL_SECONDS` of 120 s. Environment state replicates on `ENV_HEARTBEAT_INTERVAL_SECONDS` of 5 s. Admission overflow is queued with a ticket TTL of `DEFAULT_TICKET_TTL_MS` 30000 ms in `CapacityQueue`. These durability values are read from source and are provisional in the dossier's sense: no test asserts them by value (the suites covering the checkpoint coordinator, lease supervisor, resume token, environment clock, and capacity queue pass explicit intervals or consume the constant by name), unlike the lag-compensation and policy constants pinned by the fixture-hash test.

The protocol epoch is a one-way ratchet `RUNTIME_PROTOCOL_EPOCH_LADDER` of `legacy.v1`, `canonical.v1`, `kernel.v1`, enforced by the database guard `runtime_guard_protocol_activation()`; `kernel.v1` is the durability-inversion epoch (journaled and projected behind the tick) that the capacity numbers below were measured on.

The durability-honest per-loop capacity knee is `RUNTIME_LOAD_TEST_POLICY.maxLocalAuthoritativeRooms` of 128 and `maxLocalAuthoritativePlayers` of 1024.

{% warning severity="caution" title="Shard caps are provisional, not tuned SLOs" %}
`RUNTIME_SHARD_CAPACITY_POLICY` sets `maxRoomsPerInstance` 200, `maxPlayersPerInstance` 2000, `maxRoomsPerShard` 32, and `maxPlayersPerShard` 320, and it carries `provisional: true`. The code marks these as conservative placeholders re-measured per release by the capacity benchmark, never a tuned service level; any quotation of them must carry the same provisional qualifier. The per-shard room cap is also the crash blast-radius bound.
{% /warning %}

## Canonical ordering

Runtime identity ordering uses `compareCanonicalRuntimeStrings` in `server/src/modules/runtime/canonicalOrder.ts`, which compares by UTF-16 code unit so it cannot drift with host locale. It is deliberately **not** the platform warehouse collation (Unicode code-point order, equal to UTF-8 bytes and Postgres `COLLATE "C"`); the two orders differ only for supplementary-plane characters, and every current input is an engine-generated ASCII identifier for which they are identical. Do not use it for warehouse record ordering, and do not feed it non-ASCII identity strings.

## Proof surfaces

The contract's guarantees are held by static gates and tests, not by prose.

- **Contract-closure gate**: `scripts/check-runtime-netcode-contract.mjs` asserts that the accepted handshake publishes the contract, that authority frames carry its hash, that the client prediction gate routes through the shared helper, and that script prediction contracts come from the graph. One caveat below.
- **Five prediction hard gates**: `scripts/check-runtime-netcode-prediction-hardgates.mjs` enforces Gate 6 (no dynamic-physics import in the prediction path), Gate 9 (deterministic, host-allowlisted, narrow script prediction), Gate 10 (no client script op applicator), Gate 11 (both resolvers source the immutable authored graph), and Gate 12 (the collision lane reads both movement dialects).
- **Fan-out boundary**: `scripts/check-netcode-replication-fanout-boundary.mjs` restricts the authority producers to the authorized corridor files.
- **Timing-literal ratchet**: `scripts/check-netcode-timing-literals.mjs` keeps a shrink-only baseline so no new local millisecond or hertz literal enters the netcode timing paths; the detector carries a self-test.
- **Fixture-hash test**: `server/tests/runtimeNetcodeContractFixtureHash.test.ts` resolves the contract for a canonical room and graph fixture, pins the emitted contract hash, and asserts by value the lag, fairness, extrapolation, tick, input, and lane fields plus the projected policy constants (backpressure, slow-consumer, shard caps with `provisional`, and the capacity knee).

{% warning severity="info" title="What the closure gate's service check actually proves" %}
The closure gate matches the service with the greedy regex `resolveRuntimeNetcodeContract({ ... graph: worldState.graph ...`. Because the pattern spans the whole call, it is satisfied by the returned bundle field `return { graph: worldState.graph, ... }`, not by the resolve **argument**, which is `graph: worldState.authoredGraph ?? worldState.graph`. The authored-graph sourcing of the resolve argument is what Gate 11 actually pins; the closure gate proves the contract is resolved from a world-state-derived graph and that the bundle exposes the projected graph, not that the resolve argument is `worldState.graph`.
{% /warning %}
