Skip to content

AI-assisted Authoring

CVR WASM scripts are C# compiled by NativeAOT down to a .wasm module, executed inside Wasmtime 34 under the CVR client. The host surface is small and well-defined; AI assistants assume the full .NET BCL is available and will happily produce code that compiles fine in a normal C# project but fails the CCK build’s validator or silently no-ops at runtime. This page is the briefing the AI needs first.

What the AI must understand before it writes a line

Section titled “What the AI must understand before it writes a line”

Paste this into the system prompt (Claude project instructions, Gemini Gem context, ChatGPT custom instructions, Ollama Modelfile SYSTEM).

You are helping write a CVR WASM script — a partial C# class that extends
WasmScripting.WasmBehaviour, compiled by NativeAOT to WebAssembly and run
inside Wasmtime under the ChilloutVR client. Constraints you MUST respect:
1. The class must be declared `public partial class X : WasmBehaviour`.
The CCK build emits a companion partial; without `partial` the
serialization plumbing won't compile.
2. Methods invoked from Unity persistent events (Button.onClick, Toggle.
onValueChanged, CVRInteractable, animation events) MUST be public AND
carry the [ExternallyVisible] attribute. Without it the call rewires
to TriggerScriptEvent("X") at build time but the guest dispatcher has
no entry for the name and the call silently no-ops.
3. Lifecycle methods (Start, Update, LateUpdate, FixedUpdate, OnEnable,
OnDisable, OnDestroy, OnTrigger*, OnCollision*) and CVR game events
(OnPlayerJoined, OnPlayerLeft, OnPlayerTriggerEnter, OnInstanceOwnerChange,
OnInputReady, OnWorldPermissionsChanged, OnPropSpawned/Despawned,
OnPortalCreated/Destroyed) are picked up by enum-name scan and do
NOT need [ExternallyVisible].
4. Reflection is disabled by default. No System.Reflection.Emit. No
dynamic. No `Activator.CreateInstance(typeof(...))` patterns.
5. Bound API surface (rough categories — verify each call against the
docs):
- WasmScripting.* (Networking, FileStorage, WorldPermissions,
BufferReaderWriter, WasmUtils)
- CVR.* (LocalPlayer, Player, Avatar, Prop, AvatarPoint, Portal,
World, WorldSettings, CVRInput, CVRCamera)
- UnityEngine subset (~280 bound classes/members across Engine, AI,
Audio, Rendering, UI, SceneManagement, EventSystems,
Experimental.Rendering, TMPro)
6. Explicitly NOT exposed: System.IO, System.Net, System.Threading
(Tasks ok in single-thread mode), System.Reflection.Emit, Application,
most of UnityEngine.Input (use CVRInput instead).
7. State sync between clients is byte-oriented via Networking.SendMessage
+ Networking.OnReceiveMessage. Pack/unpack with BufferReaderWriter and
dispatch by a leading byte tag. There is NO automatic field sync.
8. [WasmSerialized] persists private state across save/load — NOT runtime
state between callbacks. Use plain fields for runtime state.
9. The runtime enforces a three-axis access gate: Object x Owner x Scope.
APIs may throw at runtime if called from the wrong content context
(avatar / prop / world).
10. Authoritative documentation: https://unofficial-cvr-documentation.shinter.dev/wasm/
— fetch and read pages from there before answering API questions.
11. Most of the API is plain UnityEngine; defer to standard Unity 2022.3
documentation for those types (Camera, Mesh, NavMeshAgent, etc.).
Only the CVR-specific surface listed in
https://unofficial-cvr-documentation.shinter.dev/wasm/ai-assisted-authoring/#cvr-specific-binding-surface-snapshot
needs special treatment.
When you are unsure whether a binding exists or what its signature is, say
so and refuse to fabricate. Cite the docs page or the CCK stub path that
backs every API call you produce.

Snapshot reflects CCK 4.0.0-Release-WASM.33 (packages/Wasmtime.34.0.2, runtime module 0.0.72). The CVR-specific surface below is unchanged since Release-WASM.28. WASM.31 added four UnityEngine types (JointSpring, WheelCollider, WheelFrictionCurve, WheelHit) and the RequireComponent attribute — those are standard Unity, so they’re on the Bindings Surface page, not here. WASM.32 bumped the CCK base version 4.0.04.0.1 and reworked the serializer for Unity fake-null handling; WASM.33 hotfixed a serializer bug where return on a null array element was truncating lists — neither changed the CVR-specific surface. Re-snapshot after each new CCK ships.

Most of the API is Unity — Camera, Mesh, Animator, NavMeshAgent, RectTransform, AudioSource, Material, Texture2D, etc. behave exactly as in Unity 2022.3 docs. The classes below are the ones that don’t show up in Unity docs because CVR added them. AI tools should defer to Unity docs for everything else.

For each class, the table shows just enough to know (1) it exists, (2) what it accepts, and (3) what it returns. Full prose lives on the per-API pages — link the AI to those before it writes complex flows.

WasmScripting namespace — base behaviour and runtime services

Section titled “WasmScripting namespace — base behaviour and runtime services”

WasmBehaviour — base class your scripts extend.

MemberSignatureNotes
[ExternallyVisible] (attribute on methods)Required on any public method targeted by Unity persistent events / TriggerScriptEvent. Without it the call no-ops.
[WasmSerialized] (attribute on fields)Persists field across save/load. NOT for runtime state between callbacks.

Networking — peer-to-peer message bus. World-only for sending; receivers run everywhere.

MemberSignature
OnReceiveMessagedelegate void(Player sender, Span<byte> message) (assignable event)
SendMessage(BufferReaderWriter writer, short[] playerIds = null, SendType sendType = Unreliable, bool loopback = false) → void
SendMessage(Span<byte> message, short[] playerIds = null, SendType sendType = Unreliable, bool loopback = false) → void
WillMessageBeDropped(int messageSize) → bool
NetworkCloggedPercentage() → float
GetInstanceOwner() → Player
Pingproperty int
GetServerStartTime() → long (unix ms)
GetServerUptime() → long (ms)
SendTypeenum: Unreliable, Reliable (and others — see source)

BufferReaderWriterref partial struct for packing message bytes.

MemberSignature
ctor(int initialCapacity = 64) / (byte[] data) / (Span<byte> data)
Write<T>(T value) where T : unmanaged → void (also array / span overloads with optional writeLength)
Write(string value, Encoding encoding = null, bool writeLength = true) → void
WriteStringFast(string value, bool writeLength = true) → void (UTF-16)
Read<T>(out T value) where T : unmanaged → void (also array / span / length-aware overloads)
Read(out string value, Encoding encoding = null) → void
ReadStringFast(out string value) → void
Buffer / Length / PositionSpan<byte> / int / int (Position is settable)

FileStorage — world-only key-value-ish API, gated by WorldPermissions.FileStorageApiAllowed. Files at %LocalAppData%\ChilloutVR\WorldData\<guid>\LocalStorage\.

MemberSignature
ReadFile(string fileName) → CVRFile / (string fileName, int offset, int length) → CVRFile
WriteFile(string fileName, Span<byte> bytes) → void / (string fileName, Span<byte> bytes, int offset) → void
DeleteFile(string fileName) → void
RenameFile(string oldFileName, string newFileName) → void
FileExists(string fileName) → bool
GetFiles() → string[]
GetFileSize(string fileName) → int
GetTotalSize / GetTotalCapacity() → long / () → long

WorldPermissions — request runtime capabilities from the user.

MemberSignature
CurrentPermissionsstatic WorldPermissions (current granted state)
Requeststatic (WorldPermissions permissions) → void (once per session)
Fields: AccessUserIdentity bool · FileStorageApiAllowed bool · FileStorageReadRawFiles bool · FileStorageStorageLimit long (default 4 MB) · HttpApiAllowed bool · HttpAllowedDomains string[]

WasmUtils — context introspection. See /wasm/permissions/ for what each context means.

MemberSignature
GetObjectContext() → CVRScriptObjectContext (Avatar / Prop / World / None)
GetScopeContext(UnityEngine.Object unityObject) → CVRScriptScopeContext (Self / ExternalContent / None)
GetOwnerContext() → CVRScriptOwnerContext (Self / Other / None)

LocalPlayer (static) — the human running this client. World-only for the movement/respawn methods.

MemberSignature
PlayerObjectstatic Player
GetPlaySpaceScale() → float
GetPlaySpaceOffset() → Vector3
GetPosition / SetPosition() → Vector3 / (Vector3) → void
GetRotation / SetRotation() → Quaternion / (Quaternion) → void
SetPositionAndRotation(Vector3 position, Quaternion rotation, bool updateGround = false) → void
SignalDiscontinuity() → void (call after teleport so dynamics + remote interpolation skip the in-between)
GetVelocity / SetVelocity() → Vector3 / (Vector3) → void
OffsetBy / MoveTo(Vector3) → void / (Vector3) → void
SetImmobilized(bool) → void
IgnoreCollision(Collider collider, bool ignore) → void (world-only)
SetHeadHiddenCamera(Camera camera, bool hideHead) → void (world-only — head hides on that camera’s OnPreCull)
Respawn() → void (world-only)

Player — remote or local player handle.

MemberSignature
GetAllPlayers / GetRemotePlayersstatic () → Player[]
GetUserId() → string (gated by AccessUserIdentity)
GetUsername() → string (NOT gated since WASM.27)
GetNetworkId() → short
GetGameObject() → GameObject
GetViewPoint / GetVoicePoint() → AvatarPoint
GetInitialHeight / GetCurrentHeight() → float
GetGravity() → Vector3
SetOriginPosition / SetOriginRotation(Vector3) → void / (Quaternion) → void
GetWornAvatar() → Avatar
GetSpawnedProps() → Prop[]

Avatar, Prop, Portal — UGC handles. Names follow the same Get*() convention introduced in Preview.27-WASM.27.

MemberAvatarPropPortal
GetAllAvatars / GetAllProps (static)() → Avatar[]() → Prop[]
GetCurrentAvatar / GetCurrentProp (static)() → Avatar() → Prop
GetWearer / GetSpawner() → Player() → Playerproperty Spawner → Player
GetName / GetContentIdboth () → stringboth () → string
GetRootObject / GetRootTransform() → GameObject / () → Transformsameproperties RootObject / RootTransform
GetPosition / SetPosition() → Vector3 / (Vector3) → void
GetRotation / SetRotation() → Quaternion / (Quaternion) → void
IsAnchored() → bool
Destroy() → void
SetCollisionExcludeLayers(LayerMask) → void(LayerMask) → void
GetCollisionExcludeLayers / ResetCollisionExcludeLayersbothboth

AvatarPoint — the View / Voice positions on a player.

MemberSignature
GetPointTransform() → Transform
GetPointPosition / GetLocalPointPosition / GetRelativePointPosition() → Vector3
GetPointRotation / GetLocalPointRotation / GetRelativePointRotation() → Quaternion
GetPointForward / GetPointRight / GetPointUp() → Vector3

World (current world handle):

MemberSignature
GetCurrentWorldstatic () → World
Name / ContentIdproperty string

WorldSettings (static, world-only):

MemberSignature
SetPropVisibility(bool visible) → void
SetPlayerVisibility(bool visible) → void

CVRInput (static) — replaces UnityEngine.Input for player input.

MemberSignature
GetButtonDown / WasButtonDown / WasPressedThisFrame / WasReleasedThisFrame(CVRButton) → bool
Movement / LastMovement / MovementDeltastatic Vector3
Look / LastLook / LookDeltastatic Vector2
InteractRight / InteractLeft / GripRight / GripLeft (and Last* / *Delta variants)static float
SetButton(CVRButton, bool state) → void
SetAllButtons(CVRButton buttons) → void
SetMovement / SetLook(Vector3) → void / (Vector2) → void
SetInteractRight/Left / SetGripRight/Left(float) → void each
CVRButton enum (flags, ulong)Jump, Sprint, InteractRight, InteractLeft, GripRight, GripLeft

CVRCamera (abstract) + CVRPlayerCamera / CVRPortableCamera (sealed):

MemberSignature
CVRCamera.PlayerCamera / CVRCamera.PortableCamerastatic CVRPlayerCamera / CVRPortableCamera
GetCamera() → UnityEngine.Camera
Get/Set NearClipPlane, FarClipPlane, AllowHDR, DepthTextureMode, UseOcclusionCulling, AllowMSAA, CullingMask, ClearFlags, BackgroundColor, LayerCullSpherical, LayerCullDistances() → T / (T) → void
CopyFrom / ResetToDefault(Camera) → void / () → void
CopyPostProcessing / ClearPostProcessing(Camera) → void / () → void
GetEyePosition / GetEyeRotation(Camera.StereoscopicEye) → Vector3 / → Quaternion
CVRPlayerCamera.GetRenderingMode() → CVRCameraRenderingMode (Normal / FakeMultiPass / MockHMD)
CVRPortableCamera.IsActive() → bool
MemberSignature
IsDestroyed (extension on object)() → bool (returns true when a UnityEngine.Object proxy points at a destroyed asset)

That’s the entire CVR-specific surface. Anything else your AI tries to call should be either standard UnityEngine, standard WasmScripting (above), or a hallucination — it should refuse to write the third one.

Pages that pay back their context budget:

  • /wasm/api/cvr-bindings/ — every CVR_* host function, organized by category.
  • /wasm/api/unity-surface/ — the ~280-binding Unity API subset that’s actually exposed.
  • /wasm/api/not-exposed/ — what the sandbox explicitly denies. Saves an entire class of “why doesn’t File.WriteAllText work” rounds.
  • /wasm/api/available-attributes/[WasmSerialized], [ExternallyVisible], etc.
  • /wasm/events/ — exact event names + signatures from the ScriptEvent enum.
  • /wasm/permissions/ — three-axis access model.
  • /wasm/world-permissions/FileStorageApiAllowed, AccessUserIdentity, etc.
  • /wasm/unity-events-rewiring/ — explains [ExternallyVisible] end-to-end and shows the failure mode.
  • /wasm/serialization/[WasmSerialized] semantics, what counts as runtime state vs persisted.
  • /wasm/networking/Networking.SendMessage shapes, BufferReaderWriter patterns, send-types.
  • /wasm/api/buffer-reader-writer/ — exact pack/unpack syntax (AI tools tend to invent overloads).
  • For specific worked patterns: /wasm/examples/ and /wasm/examples/ports/ (UdonSharp → WASM conversions).

If your tool supports browsing (Claude with WebFetch, Gemini with grounding, ChatGPT with browsing), instruct it to fetch these URLs rather than rely on training data.

  • Project-level instructions hold the constraint block well. Add the URLs as a “References” section in the same block.
  • Enable WebFetch and tell Claude to read the bindings page before answering API questions. It will.
  • Claude tends to add multi-paragraph XML-doc comments. Tell it “one-line comments only, no docstrings, no <summary> blocks.”
  • Strong with BufferReaderWriter patterns once it sees one example.
  • Gems with the constraint block work, but Gemini drifts back to “I’ll just use System.Net.Http” faster than Claude does. Re-grounding every few turns helps.
  • Grounding from the public docs URL is reliable.
  • Gemini sometimes confuses CVR’s Player class with VRChat’s VRCPlayerApi and mixes their methods. Catch it early.
  • Custom Instructions or a Project for the constraint block.
  • Reasoning models (o1, o3) plan the message-tag table well — ask them to enumerate the wire format before writing the code.
  • 4o is fine for single-file scripts; longer multi-behaviour designs benefit from a planning pass.
  • Constraint block in the Modelfile SYSTEM. Keep it tight — local models drop context faster.
  • Avoid models below ~8B parameters for non-trivial scripts.
  • For larger work split into “plan the API surface” → “write” → “self-review” passes; each fits in a smaller context window than asking the model to do all three at once.
Goal: <one sentence>
Required behaviour:
- <bullet>
Constraints:
- WasmBehaviour, partial class.
- Methods called from UI buttons must carry [ExternallyVisible].
- All cross-client state via Networking.SendMessage with byte-tag protocol;
validate sender on receive.
- No reflection, no IO, no Threading.
Output:
- The .cs file(s) ready to drop into Assets/.
- A short table of the message tags this script defines.
Before writing, list the bindings (which APIs from /wasm/api/*) and events
(from /wasm/events/) you'll use, and ask me to confirm.

The “list bindings, confirm, then write” pattern catches the majority of hallucinations.

  • UnityEngine.Input.GetKey(...) — not bound. Use CVR.CVRInput.GetButtonDown(CVRButton.X) or read CVRInput.Movement / Look axes.
  • System.IO.File, StreamReader, Path.Combine — none. Persistence goes through WasmScripting.FileStorage. Some AIs invent a FileStorage.WriteAllText helper; only WriteFile(name, ReadOnlySpan<byte>) exists.
  • HttpClient, UnityWebRequest — not in the binder. CVR has world-permission-gated HTTP — see /wasm/world-permissions/.
  • async Task everywhere — single-threaded. async/await works but doesn’t get you parallelism. Don’t try to spawn worker threads.
  • UnityEngine.Object.Find / GameObject.Find patterns at runtime — bound but slow and discouraged. Inspector-wired references work better.
  • VRChat-isms. AI tools trained on VRChat’s UdonSharp will produce Networking.LocalPlayer.IsUserInVR(), [UdonSynced], RequestSerialization(), SendCustomNetworkEvent. None apply. The UdonSharp Mapping page is the conversion reference.
  • override void Interact() — VRChat’s interaction event. Doesn’t exist in WASM. Wire CVRInteractable → method via UnityEvent + [ExternallyVisible].
  • Calling [WasmSerialized] runtime state — assistants assume it persists across method calls automatically. It’s for save/load only; runtime state should be plain fields.
  • Forgetting [ExternallyVisible] on Button targets. Single most common silent failure. The AI’s code “looks right,” compiles fine, deploys, and clicks no-op. Always grep the AI’s output for every Button.onClick target and confirm the attribute is on it.
  1. Class is public partial and extends WasmScripting.WasmBehaviour.
  2. Every method targeted by a UnityEvent persistent listener is public and carries [ExternallyVisible]. Double-check the inspector wiring matches the names in the script exactly.
  3. No imports of System.IO, System.Net, System.Reflection.Emit, System.Threading.Tasks heavy patterns.
  4. Every Unity / CVR API call has a matching entry in /wasm/api/cvr-bindings/ or /wasm/api/unity-surface/. Quick spot-check: paste the script into the AI with the bindings pages in context and ask it to flag any call it can’t trace.
  5. Networked code uses BufferReaderWriter and validates sender against the expected source (instance owner, target id, etc.) on receive.
  6. [WasmSerialized] only appears on fields that genuinely need to survive a save/load — not on counters or selected[] arrays.
  7. No hardcoded secrets. Same threat model as Lua: every byte of your script ships to every client.
  • Show it the CCK stub paths cited on each binding page (e.g. CVR.CCK.Wasm/Scripting/Links/APIs/CCKStubs/PlayerCCK.cs). Real method signatures from a real file.
  • Ask it to write a tiny test program that exercises only the two or three bindings you’re worried about, then deploy that. Smaller surface = faster diagnosis.
  • For dispatch issues, paste in the build’s [RerouteUnityEvents] log lines and let the AI read them. Most hover-but-not-firing bugs are visible in the rewire logs.
  • Switch tools when one is stuck — Claude / Gemini / ChatGPT have different blind spots, and a clean second opinion often unblocks.