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 extendsWasmScripting.WasmBehaviour, compiled by NativeAOT to WebAssembly and runinside 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, sayso and refuse to fabricate. Cite the docs page or the CCK stub path thatbacks every API call you produce.CVR-specific binding surface (snapshot)
Section titled “CVR-specific binding surface (snapshot)”Snapshot reflects CCK 4.0.0-Release-WASM.33 (
packages/Wasmtime.34.0.2, runtime module0.0.72). The CVR-specific surface below is unchanged since Release-WASM.28. WASM.31 added four UnityEngine types (JointSpring,WheelCollider,WheelFrictionCurve,WheelHit) and theRequireComponentattribute — those are standard Unity, so they’re on the Bindings Surface page, not here. WASM.32 bumped the CCK base version4.0.0→4.0.1and reworked the serializer for Unity fake-null handling; WASM.33 hotfixed a serializer bug wherereturnon 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.
| Member | Signature | Notes |
|---|---|---|
[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.
| Member | Signature |
|---|---|
OnReceiveMessage | delegate 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 |
Ping | property int |
GetServerStartTime | () → long (unix ms) |
GetServerUptime | () → long (ms) |
SendType | enum: Unreliable, Reliable (and others — see source) |
BufferReaderWriter — ref partial struct for packing message bytes.
| Member | Signature |
|---|---|
| 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 / Position | Span<byte> / int / int (Position is settable) |
FileStorage — world-only key-value-ish API, gated by WorldPermissions.FileStorageApiAllowed. Files at %LocalAppData%\ChilloutVR\WorldData\<guid>\LocalStorage\.
| Member | Signature |
|---|---|
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.
| Member | Signature |
|---|---|
CurrentPermissions | static WorldPermissions (current granted state) |
Request | static (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.
| Member | Signature |
|---|---|
GetObjectContext | () → CVRScriptObjectContext (Avatar / Prop / World / None) |
GetScopeContext | (UnityEngine.Object unityObject) → CVRScriptScopeContext (Self / ExternalContent / None) |
GetOwnerContext | () → CVRScriptOwnerContext (Self / Other / None) |
CVR namespace — game-side surface
Section titled “CVR namespace — game-side surface”LocalPlayer (static) — the human running this client. World-only for the movement/respawn methods.
| Member | Signature |
|---|---|
PlayerObject | static 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.
| Member | Signature |
|---|---|
GetAllPlayers / GetRemotePlayers | static () → 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.
| Member | Avatar | Prop | Portal |
|---|---|---|---|
GetAllAvatars / GetAllProps (static) | () → Avatar[] | () → Prop[] | — |
GetCurrentAvatar / GetCurrentProp (static) | () → Avatar | () → Prop | — |
GetWearer / GetSpawner | () → Player | () → Player | property Spawner → Player |
GetName / GetContentId | both () → string | both () → string | — |
GetRootObject / GetRootTransform | () → GameObject / () → Transform | same | properties RootObject / RootTransform |
GetPosition / SetPosition | — | — | () → Vector3 / (Vector3) → void |
GetRotation / SetRotation | — | — | () → Quaternion / (Quaternion) → void |
IsAnchored | — | — | () → bool |
Destroy | — | () → void | — |
SetCollisionExcludeLayers | (LayerMask) → void | (LayerMask) → void | — |
GetCollisionExcludeLayers / ResetCollisionExcludeLayers | both | both | — |
AvatarPoint — the View / Voice positions on a player.
| Member | Signature |
|---|---|
GetPointTransform | () → Transform |
GetPointPosition / GetLocalPointPosition / GetRelativePointPosition | () → Vector3 |
GetPointRotation / GetLocalPointRotation / GetRelativePointRotation | () → Quaternion |
GetPointForward / GetPointRight / GetPointUp | () → Vector3 |
World (current world handle):
| Member | Signature |
|---|---|
GetCurrentWorld | static () → World |
Name / ContentId | property string |
WorldSettings (static, world-only):
| Member | Signature |
|---|---|
SetPropVisibility | (bool visible) → void |
SetPlayerVisibility | (bool visible) → void |
CVRInput (static) — replaces UnityEngine.Input for player input.
| Member | Signature |
|---|---|
GetButtonDown / WasButtonDown / WasPressedThisFrame / WasReleasedThisFrame | (CVRButton) → bool |
Movement / LastMovement / MovementDelta | static Vector3 |
Look / LastLook / LookDelta | static 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):
| Member | Signature |
|---|---|
CVRCamera.PlayerCamera / CVRCamera.PortableCamera | static 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 |
WasmScripting.ProxyExtensions
Section titled “WasmScripting.ProxyExtensions”| Member | Signature |
|---|---|
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.
URLs to feed the AI (or have it fetch)
Section titled “URLs to feed the AI (or have it fetch)”Pages that pay back their context budget:
/wasm/api/cvr-bindings/— everyCVR_*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’tFile.WriteAllTextwork” rounds./wasm/api/available-attributes/—[WasmSerialized],[ExternallyVisible], etc./wasm/events/— exact event names + signatures from theScriptEventenum./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.SendMessageshapes,BufferReaderWriterpatterns, 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.
Tool-specific notes
Section titled “Tool-specific notes”Claude
Section titled “Claude”- 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
BufferReaderWriterpatterns once it sees one example.
Gemini
Section titled “Gemini”- 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
Playerclass with VRChat’sVRCPlayerApiand mixes their methods. Catch it early.
ChatGPT (GPT-4o / o1 / o3 / 4.5)
Section titled “ChatGPT (GPT-4o / o1 / o3 / 4.5)”- 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.
Ollama / local models
Section titled “Ollama / local models”- 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.
A user-message prompt template
Section titled “A user-message prompt template”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.
Common pitfalls AI assistants fall into
Section titled “Common pitfalls AI assistants fall into”UnityEngine.Input.GetKey(...)— not bound. UseCVR.CVRInput.GetButtonDown(CVRButton.X)or readCVRInput.Movement/Lookaxes.System.IO.File,StreamReader,Path.Combine— none. Persistence goes throughWasmScripting.FileStorage. Some AIs invent aFileStorage.WriteAllTexthelper; onlyWriteFile(name, ReadOnlySpan<byte>)exists.HttpClient,UnityWebRequest— not in the binder. CVR has world-permission-gated HTTP — see/wasm/world-permissions/.async Taskeverywhere — single-threaded.async/awaitworks but doesn’t get you parallelism. Don’t try to spawn worker threads.UnityEngine.Object.Find/GameObject.Findpatterns 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. WireCVRInteractable→ 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 everyButton.onClicktarget and confirm the attribute is on it.
Verification checklist before you ship
Section titled “Verification checklist before you ship”- Class is
public partialand extendsWasmScripting.WasmBehaviour. - Every method targeted by a UnityEvent persistent listener is
publicand carries[ExternallyVisible]. Double-check the inspector wiring matches the names in the script exactly. - No imports of
System.IO,System.Net,System.Reflection.Emit,System.Threading.Tasksheavy patterns. - 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. - Networked code uses
BufferReaderWriterand validatessenderagainst the expected source (instance owner, target id, etc.) on receive. [WasmSerialized]only appears on fields that genuinely need to survive a save/load — not on counters or selected[] arrays.- No hardcoded secrets. Same threat model as Lua: every byte of your script ships to every client.
What to do when the AI is stuck
Section titled “What to do when the AI is stuck”- 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.
See also
Section titled “See also”- Quickstart — manual authoring path.
- Authoring — the full lifecycle.
- Unity Events Rewiring —
[ExternallyVisible]end-to-end. - Permissions — three-axis access model.
- UdonSharp Mapping — for AIs (or humans) coming from VRChat.
- Examples — patterns the AI can study.