Limits
TL;DR: Each WASM call must complete within a ~20 ms epoch deadline (worlds); overrun traps the Store. WASM threads are disabled. File storage defaults to a 4 MB per-world quota. There is no explicit memory cap in the decompile — memory growth is bounded by the host process’s available memory and Wasmtime’s own guarded heap.
Epoch preemption
Section titled “Epoch preemption”File: CVR-GameFiles/WasmScripting/WasmManager.cs.
// Timer thread target cadence = 100 kHz (every ~10 µs):long targetTicks = Stopwatch.Frequency / 100_000;
// Per-call deadline for every script:private const ulong EpochDeadlineTicks = 2000uL; // 2000 ticks- The shared
EnginehasWithEpochInterruption(true). - A background
TimerThreadbusy-spins (Thread.SpinWait(1000)inside the loop) and callsEngine.IncrementEpoch()roughly every 10 µs whenever any Store has an active call. WasmManager.StartEpochTimer(store)setsstore.SetEpochDeadline(2000uL)and wakes the timer thread through anAutoResetEvent.StopEpochTimer()clears the flag so the thread parks again.- When the counter hits the deadline mid-call the guest traps. The host catches
TrapException, logs it, and setsIsCrashed = trueon theWasmVM— no further events dispatched on that VM. - Startup path gets a temporary wider budget:
_store.SetEpochDeadline(100000uL)insideWasmVM.Setup(roughly 1 s) coversscripting_initialize+ the per-behaviourscripting_create_instance+scripting_deserialize_instancecalls, thenStopEpochTimerparks the thread until the first runtime event.
There is no separate deadline for avatars vs props vs worlds in the current decompile. The EpochDeadlineTicks = 2000 constant applies uniformly.
What counts against the deadline
Section titled “What counts against the deadline”Only guest-side wall-clock time between entering and leaving WASM. If the guest calls a host binding that itself takes time (e.g. a networking call), that time is host-side and does not tick the epoch — but the total call still has to complete within the deadline because the epoch keeps incrementing.
What the deadline does not enforce
Section titled “What the deadline does not enforce”- Separate deadlines for
Update,FixedUpdate,OnCollisionEnter, etc. — each is a separate call, each gets its own fresh 20 ms budget. - Cumulative cost. A script can spend 19 ms every frame indefinitely.
Variable FixedUpdate rate
Section titled “Variable FixedUpdate rate”In CVR, Time.fixedDeltaTime scales with the user’s display refresh rate (30 Hz → 144 Hz). The timestep remains consistent within a client, but does not match Unity’s default 0.02 across users. Don’t hard-code the expected rate; read Time.fixedDeltaTime or use Time.time deltas.
WASM feature flags
Section titled “WASM feature flags”From WasmManager.Awake():
| Feature | Setting | Comment |
|---|---|---|
| Epoch interruption | Enabled | WithEpochInterruption(true) |
| Reference types | Enabled | externref allowed |
| Multi-value | Enabled | multi-return functions |
| Bulk memory | Enabled | memory.copy, memory.fill |
| Debug info | Enabled | useful stack traces in Debug builds |
| SIMD | Enabled | v128 |
| WASM exceptions | Enabled | via P/Invoke wasmtime_config_wasm_exceptions_set(handle, true) because the .NET wrapper doesn’t expose this flag |
| WASM threads | Not set | WithWasmThreads(true) is never called — the shared atomics / thread proposal is off |
Memory
Section titled “Memory”- Wasmtime defaults apply. No explicit
WithMaxMemorySize/WithStoreLimitsin the decompile. - In practice the host process’s physical memory is the only upper bound; an OOM from a runaway
memory.growwill tear down the Store. - No per-call allocation accounting.
File storage
Section titled “File storage”See File Storage for the API.
- Default quota:
4_194_304bytes (4 MB) per world, defined inCVR-GameFiles/WasmScripting/WorldPermissions.cs. - User-adjustable: 1 KB to ~17 TB via the
WasmPermissionsPagepalette. - Requires world permission:
FileStorageApiAllowed. Without it everyFileStorage_*call errors.
Networking
Section titled “Networking”See Networking. The constants below are from CVR-GameFiles/WasmScripting/WasmNetworkManager.cs.
| Constant | Value | Applies to |
|---|---|---|
MaxBytesPerSecond | 4096 | Sustained send budget per VM. Drained at 4 KB/s of wall clock. |
MaxBytesSendSpike | 16384 | Instantaneous cap on in-flight bytes. CVR_Networking_NetworkCloggedPercentage returns bytesSent / 16384. |
MaxBytesQueued | 65536 | Upper bound on the per-VM queue. CVR_Networking_WillMessageBeDropped(size) returns true if queuedBytes + size > 65536. |
Message header overhead: "CVR.TN:1.0.0".Length * 2 + playerIds.Length * 4 + message.Length + 22 bytes. Any outbound message that would push the header+payload above MaxBytesSendSpike (16 KB) is dropped silently at WasmNetworkManager.SendMessage.
SendType values (CVR-GameFiles/WasmScripting/SendType.cs): Unreliable = 0, UnreliableSequenced = 1, Reliable = 2. Values 0 / 1 route through DarkRift’s unreliable channel; 2 through the reliable channel.
Disabled APIs (recap)
Section titled “Disabled APIs (recap)”See Not Exposed for the full list. Brief:
- Filesystem — use the sandboxed
FileStorage_*API instead. - Network sockets — use
CVR_Networking_*or world-permissioned HTTP. - Process spawning, reflection emit, application-lifetime calls.
Startup cost
Section titled “Startup cost”- First CCK build in a fresh Unity project downloads ~300 MB of toolchain (.NET SDK + WASI SDK). Subsequent builds are fast.
- Per VM startup: one module compile if the cache misses (roughly linear in
.wasmsize, seconds for small modules); then a cheapModule.DeserializeFilefor subsequent loads (~tens of ms). - Per VM instantiation +
scripting_initialize+ per-behaviour_createInstance: typically low-single-digit ms for a small module.
Things to avoid
Section titled “Things to avoid”- Tight loops without natural return points in
Update— risk epoch trap. - Allocating large arrays each frame — GC churn plus risk of memory growth that never shrinks.
- Calling many host bindings per frame per behaviour — each is a WASM-host transition with locks; batch where possible.
- Long
forloops over players in worlds with large instance caps — preferOnPlayerJoined/OnPlayerLeftevents and maintain your own list.
File storage file limits
Section titled “File storage file limits”From CVR-GameFiles/WasmScripting/FileStorageManager.cs:
| Constant | Value | Meaning |
|---|---|---|
MaxFileNameCharacterLimit | 100 | Characters accepted per filename. Extra characters are silently truncated after disallowed-character sanitization. |
MaxFileLimit | 1000 | Upper bound on the number of .wasmdata files per world. New writes fail once this is reached. |
Filename sanitization strips every Path.GetInvalidFileNameChars() character before checking the length cap.
Source
Section titled “Source”CVR-GameFiles/WasmScripting/WasmManager.cs—EpochDeadlineTicks = 2000, timer thread, feature-flag config.CVR-GameFiles/WasmScripting/WasmVM.cs— one-time100000uLstartup deadline insideSetup.CVR-GameFiles/WasmScripting/WasmNetworkManager.cs— networking budget constants.CVR-GameFiles/WasmScripting/FileStorageManager.cs— file count + filename-length limits.CVR-GameFiles/WasmScripting/WorldPermissions.cs— default storage quota.
Related
Section titled “Related”- Architecture — where the timer lives.
- Runtime Lifecycle — when the deadline gets set.
- File Storage — exact quota semantics.