Skip to content
cccskills
FreePublished on GitHub

sbox

Use when writing or modifying code for s&box, sbox, the Facepunch sandbox engine, or any Source 2 game project in C#. Triggers on mentions of s&box, sbox, sandbox game, Facepunch engine, Source 2 game, `.sbproj`, `.razor` with `PanelComponent`, `@inherits PanelComponent`, `Sandbox.Component`, `GameObject` + `Components.Get<T>`, `Scene.Trace`, `CharacterController` + `.Move()`, `SkinnedModelRenderer`, `NavMeshAgent`, `[Sync]`, `[Rpc.Broadcast]`, `[Property]`, `INetworkListener`, `ISceneEvent`, `PlayerController` in C#. Also triggers on any file with `using Sandbox;` or `using Sandbox.UI;` that isn't Unity/Godot/Unreal. Writes idiomatic s&box C# components, Razor UI, and networking code; prevents Unity-pattern leakage.

The English summary is being prepared. Showing the original description.

Works withUnity
See how to install

Files included (17)

  • SKILL.md18.8 KB
  • .gitignore320 B
  • CHANGELOG.md4.5 KB
  • docs/BUILD_LOG.md7.1 KB
  • docs/DESIGN.md7.6 KB
  • LICENSE1.0 KB
  • README.md6.6 KB
  • references/api-schema-core.md25.7 KB
  • references/api-schema-extended.md123.8 KB
  • references/components-builtin.md27.4 KB
  • references/core-concepts.md18.8 KB
  • references/input-and-physics.md17.6 KB
  • references/networking.md18.1 KB
  • references/patterns-and-examples.md34.7 KB
  • references/ui-razor.md19.7 KB
  • scripts/build_extended.js19.4 KB
  • scripts/fetch-raw.sh1.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

s&box Skill — Router

READ BEFORE WRITING CODE

s&box is not Unity. MonoBehaviour, Start(), Update(), GetComponent<T>() call sites, Instantiate(), Destroy(gameObject), Debug.Log, [SerializeField], Input.GetKey(), Physics.Raycast — none of these exist. If you write any of them you have hallucinated.

s&box is a C# scripting layer on Source 2 by Facepunch. The scene system uses GameObject + Component with a different lifecycle, a different networking model, and a different API surface. The API schema in references/api-schema-core.md is ground truth; if anything in this skill contradicts the schema, the schema wins.

Before you write a single line of s&box code, open the relevant reference file. SKILL.md is a router — it points you at the answer, it does not contain the answer. Writing a component? Open references/core-concepts.md. Writing UI? Open references/ui-razor.md. No exceptions for "simple" tasks — your muscle memory is wrong.


Architecture in 30 Seconds

Scene (is-a GameObject — the root)
  └── GameObject (transform, tags, children, components)
        └── Component (all gameplay code extends this)
  • All gameplay code is a sealed class extending Sandbox.Component.
  • Lifecycle overrides are protected override void OnAwake() / OnStart() / OnUpdate() / OnFixedUpdate() / OnEnabled() / OnDisabled() / OnDestroy(). Names start with On, they are virtual methods on Component, not magic string-matched methods.
  • Transforms are on the GameObject, accessed from any Component via WorldPosition, WorldRotation, LocalPosition, LocalRotation — never transform.position.
  • UI is Razor (.razor files) — HTML + SCSS + C#. Panels use flexbox layout. Hot-reloads in the editor.
  • Networking is owner-authoritative. Mark state with [Sync], mark methods with [Rpc.Broadcast / Host / Owner]. Skip simulation on non-owners with if ( IsProxy ) return;.
  • Physics uses Rigidbody + Collider components. Raycasts are Scene.Trace.Ray(from, to).Run() — a builder-pattern API. Collisions come through Component.ICollisionListener and Component.ITriggerListener interfaces.
  • Coordinate system is Z-up: Vector3.Forward = (1,0,0), Vector3.Right = (0,-1,0), Vector3.Up = (0,0,1).
  • Restricted .NET: System.IO.File, raw sockets, Console, Thread, Process — all blocked. Use FileSystem.Data, Http, Log, async/await.

Routing Table — "I need to…"

Match the task, open the file. Do not guess; open the file.

TaskRead
Understand the scene/GameObject/Component modelreferences/core-concepts.md
Write a Component (lifecycle, [Property], Tags, async)references/core-concepts.md
Spawn / clone / destroy a prefabreferences/core-concepts.md → Prefabs
Fire a scene event (ISceneEvent<T>)references/core-concepts.md → Scene Events
Write a GameObjectSystemreferences/core-concepts.md → GameObjectSystem
Use ModelRenderer / SkinnedModelRenderer / bones / animgraphreferences/components-builtin.md → Rendering
Use Rigidbody, any Collider, jointsreferences/components-builtin.md → Physics
Use CharacterController for movementreferences/components-builtin.md → CharacterController
Use the full PlayerController (built-in FPS/TPS)references/components-builtin.md → Gameplay
Set up a camera, HUD painter, post-processingreferences/components-builtin.md → Camera, Post-Processing
Use lights, fog, envmap probes, skyboxreferences/components-builtin.md → Lighting, Environment
Add audio (SoundPointComponent, Sound.Play)references/components-builtin.md → Audio
Use NavMeshAgent, NavMeshLink, query NavMeshreferences/components-builtin.md → Navigation
Create particles, decals, trails, beamsreferences/components-builtin.md → Effects, Rendering
Write a Razor UI panel (.razor, PanelComponent, BuildHash)references/ui-razor.md
Style with SCSS — flexbox, transitions, :intro / :outro, :bindreferences/ui-razor.md → Styling, Layout System, Transitions
Use built-in controls (Button, TextEntry, DropDown, VirtualList)references/ui-razor.md → Built-in Controls
Build a world-space panel or a NavigationHost appreferences/ui-razor.md → WorldPanel, Navigation
Set up a lobby, connect/disconnect, query Connectionreferences/networking.md → Lobby & Connection
Network an object (NetworkMode, NetworkSpawn, ownership)references/networking.md → Networked Objects, Ownership
Use [Sync] / [Sync(SyncFlags.X)] / [Change] / NetList / NetDictionaryreferences/networking.md → [Sync] Properties
Write RPCs ([Rpc.Broadcast/Host/Owner], NetFlags, caller info, filtering)references/networking.md → RPC Messages
React to connections (INetworkListener, INetworkSpawn, snapshot data)references/networking.md → Network Events
Use ISceneStartup for host vs client initializationreferences/networking.md → Scene Startup
Dedicated server / #if SERVER / user permissionsreferences/networking.md → Dedicated Servers
Poll keyboard/mouse/controller input, haptics, glyphsreferences/input-and-physics.md → Input
Raycast / sphere / box / capsule trace with tag filtersreferences/input-and-physics.md → SceneTrace
Access PhysicsWorld, gravity, physics eventsreferences/input-and-physics.md → Physics World
Implement collision/trigger listenersreferences/input-and-physics.md → Collision System
Use Vector3 / Rotation / Angles / Transform / BBox / Ray / Capsulereferences/input-and-physics.md → Math Types
Use Time.Now, Time.Delta, TimeSince, TimeUntilreferences/input-and-physics.md → Time
Draw debug gizmos (DrawGizmos, Gizmo.Draw)references/input-and-physics.md → Gizmo
Need the full signature of GameObject, Component, Scene, Input, etc.references/api-schema-core.md
Look up whether a given type exists & what it doesreferences/api-schema-extended.md
See a complete worked example of a pattern before writing your ownreferences/patterns-and-examples.md

Unity → s&box Translation Table

Any time you write one of the left column, you are hallucinating. Use the right column.

Unity / Wrongs&box / Correct
class Foo : MonoBehaviourpublic sealed class Foo : Component
void Awake()protected override void OnAwake()
void Start()protected override void OnStart()
void Update()protected override void OnUpdate()
void FixedUpdate()protected override void OnFixedUpdate()
void OnEnable() / OnDisable()protected override void OnEnabled() / OnDisabled()
void OnDestroy()protected override void OnDestroy()
[SerializeField] float speed[Property] public float Speed { get; set; }
[HideInInspector][Hide]
transform.positionWorldPosition (or GameObject.WorldPosition)
transform.localPositionLocalPosition
transform.rotationWorldRotation
transform.forwardWorldRotation.Forward
gameObject.SetActive(false)GameObject.Enabled = false
Destroy(gameObject) / Destroy(this)GameObject.Destroy() / Component.Destroy() / DestroyGameObject()
Instantiate(prefab, pos, rot)prefab.Clone( pos, rot )
Instantiate(prefab); NetworkServer.Spawn(...)prefab.Clone(pos).NetworkSpawn( owner )
GetComponent<T>() in Start/UpdateGetComponent<T>() is fine; also Components.Get<T>( FindMode ) for ancestor/descendant searches
FindObjectOfType<T>() / FindObjectsOfType<T>()Scene.Get<T>() / Scene.GetAll<T>() / Scene.GetAllComponents<T>()
GameObject.Find("Name")Scene.Directory.FindByName("Name")
OnCollisionEnter(Collision c)Implement Component.ICollisionListener.OnCollisionStart(Collision c)
OnTriggerEnter(Collider c)Implement Component.ITriggerListener.OnTriggerEnter(Collider c)
Physics.Raycast(...)Scene.Trace.Ray( from, to ).Run() (builder; returns SceneTraceResult)
Physics.OverlapSphere(pos, r)Scene.Trace.Sphere( r, pos, pos ).RunAll()
Rigidbody.AddForce(f, ForceMode.Impulse)Rigidbody.ApplyImpulse( f )
Rigidbody.AddForce(f)Rigidbody.ApplyForce( f )
Rigidbody.velocityRigidbody.Velocity (capital V)
Input.GetKey(KeyCode.W)Input.Down( "forward" ) — actions are strings configured in Project Settings
Input.GetKeyDown(...)Input.Pressed( "action" )
Input.GetAxis("Horizontal") / VerticalInput.AnalogMove (Vector3)
Input.mousePositionMouse.Position (Vector2)
Camera.mainScene.Camera
Camera.main.ScreenPointToRay(Input.mousePosition)Scene.Camera.ScreenPixelToRay( Mouse.Position )
StartCoroutine(Foo()) with IEnumeratorasync Task Foo() with await Task.DelaySeconds(...); call as _ = Foo();
yield return new WaitForSeconds(1f)await Task.DelaySeconds( 1f )
yield return nullawait Task.Frame()
Debug.Log(x) / .LogWarning / .LogErrorLog.Info(x) / Log.Warning(x) / Log.Error(x)
Time.timeTime.Now
Time.deltaTimeTime.Delta
Time.fixedDeltaTimeScene.FixedDelta
Mathf.Lerp / Clamp / ApproachMathX.Lerp / Clamp / Approach
Random.Range(a, b)Game.Random.Next(a, b) / Game.Random.NextSingle()
Vector3.forward = (0,0,1)Vector3.Forward = (1,0,0) — s&box is Z-up; re-check every literal direction
SceneManager.LoadScene("name")Scene.LoadFromFile("path/to/scene.scene") or Scene.Load( sceneResource )
DontDestroyOnLoad(go)go.Flags = GameObjectFlags.DontDestroyOnLoad
Application.isPlayingGame.IsPlaying (or !Game.IsEditor)
System.IO.File.ReadAllText(...)FileSystem.Data.ReadAllText(...) / FileSystem.Mounted.ReadAllText(...)
UnityEngine.Networking.UnityWebRequestHttp.RequestStringAsync(...) / Http.RequestJsonAsync<T>(...)
Update() reads Input.* AND moves rigidbodyRead input in OnUpdate, move in OnFixedUpdate

If a Unity pattern isn't in the table, assume it doesn't exist in s&box and look it up in references/api-schema-core.md before writing it.


The Ten Rules You Must Not Break

  1. Every gameplay class extends Component. Not MonoBehaviour, not object, not ScriptableObject — just Component. Mark it sealed unless inheritance is required.
  2. Lifecycle methods are protected override void On*(). If you wrote void Update() instead of protected override void OnUpdate(), your code does nothing.
  3. Serialize fields with [Property]. Not [SerializeField], not public alone. [Property] both shows in the inspector and saves to prefab/scene.
  4. Networked state uses [Sync]. Only the object owner may assign. Everyone else sees replicated values. Combine with [Change(nameof(Method))] for change callbacks.
  5. Guard networked logic with if ( IsProxy ) return;. Every component that reads input or drives movement starts with this line — otherwise every client tries to move every player.
  6. Ray/box/sphere traces go through Scene.Trace. It's a builder: Scene.Trace.Ray(from, to).UseHitboxes(true).WithoutTags(new[]{"player"}).Run() returns a SceneTraceResult. Never Physics.Raycast.
  7. UI is Razor + flexbox. display: flex is the default and effectively the only layout. display: block does not exist. Properties :intro / :outro animate creation and deletion. Every root panel overrides BuildHash() to control re-render.
  8. Coroutines don't exist — use async Task. await Task.DelaySeconds( n ), await Task.Frame(). Fire-and-forget with _ = MyTask();. The Component.Task property scopes cancellation to the GameObject lifetime.
  9. Never touch blocked .NET APIs. System.IO.File, Console, Thread, raw sockets, System.Net.Http.HttpClient — use FileSystem.Data, Log, async/await, Http instead. Code that references these won't compile in the sandbox.
  10. Look up every API before you use it. The schema is ground truth. If you can't find a method in api-schema-core.md or api-schema-extended.md, either you're guessing (stop) or it's nested on a specific type (search the topical reference).

Project Structure (s&box Game)

An s&box game project (.sbproj) typically looks like:

MyGame/
├── MyGame.sbproj                 # project manifest
├── code/                         # C# source (gameplay code)
│   ├── GameManager.cs
│   ├── Player.cs
│   ├── weapons/
│   │   └── Rifle.cs
│   └── ui/
│       ├── Hud.razor             # Razor panel
│       ├── Hud.razor.scss        # auto-loaded stylesheet
│       └── InventoryPanel.razor
├── prefabs/                      # .prefab files
├── scenes/                       # .scene files
├── models/                       # .vmdl, .vmdl_c
├── materials/                    # .vmat, .vmat_c
├── sounds/                       # .sound, .wav, .mp3
├── ui/                           # UI images, textures
└── localization/
    └── en/
        └── mygame.json

Notable:

  • .razor and .razor.scss files pair by name — the stylesheet auto-loads when the panel is built.
  • .cs files are hot-reloaded in the editor; saving a file rebuilds and re-injects within seconds.
  • Asset paths in code are forward-slash strings rooted at the project: Model.Load( "models/dev/box.vmdl" ).
  • There is no Assets/ folder; paths are flat under the project root.

A Reference Component (Shape Only — Not the Content)

This is the shape of a component. The content depends on what you're building — read the referenced file, then write it.

using Sandbox;

public sealed class MyComponent : Component
{
    [Property] public float Speed { get; set; } = 200f;
    [Property] public GameObject Target { get; set; }

    [Sync] public int Score { get; set; }

    TimeSince _lastAction;

    protected override void OnStart()
    {
        // runs once before first update
    }

    protected override void OnUpdate()
    {
        if ( IsProxy ) return;        // networking guard
        if ( !Target.IsValid() ) return;

        // per-frame logic (input, visuals, camera)
    }

    protected override void OnFixedUpdate()
    {
        if ( IsProxy ) return;
        // physics / movement — deterministic timestep
    }

    [Rpc.Broadcast]
    public void PlayEffect( Vector3 position )
    {
        // runs on all clients — cosmetic / non-authoritative
    }
}

For complete runnable examples — a full FPS controller, a networked player, a Razor HUD, a hitscan weapon, a NavMeshAgent AI, a physics grenade, a prefab spawner, a trigger pickup — see references/patterns-and-examples.md.


Gotchas Captured From Real Builds

These are things that will bite you. They're documented deeper in the reference files; this is the quick-lookup list.

  • ICollisionListener parameter names are collision, not other (the raw docs are wrong about this).
  • Color, Capsule, Vector3, Rotation, Angles, Transform, BBox, Ray are global types, not Sandbox.*.
  • LobbyConfig and LobbyPrivacy live in Sandbox.Network — you need using Sandbox.Network;.
  • Scene is-a GameObject — it is the root GameObject. Scene.GetAllObjects(true) walks the tree.
  • Most Component.ISomething interfaces are nested on Component: Component.IDamageable, Component.ICollisionListener, Component.ITriggerListener, Component.INetworkListener, Component.INetworkSpawn. But IGameObjectNetworkEvents is top-level Sandbox.IGameObjectNetworkEvents.
  • ComponentList.GetOrCreate<T>( FindMode ) requires the FindMode arg. For the common "on this GameObject" case, use GetOrAddComponent<T>() on GameObject or Component instead.
  • SceneTrace.WithoutTags / WithAnyTags / WithAllTags take string[] — not params. Pass new[] { "tag" }, or use WithTag(string) for the single-tag case.
  • Game.Random is System.Random — NextSingle(), Next(n) work directly. The FromList extension exists but requires a defVal; list[Game.Random.Next(list.Count)] is simpler.
  • FileSystem is a static facade. Actual methods are on BaseFileSystem, accessed via FileSystem.Data (game user data) or FileSystem.Mounted (mounted content).
  • There is no standalone Log class — it's Sandbox.Diagnostics.Logger, but the global Log instance works fine everywhere.
  • PlayerController.TraceBody has 4 parameters (the 4th is heightScale), not 3.
  • Operators (Rotation * Rotation, Vector3 * Rotation) aren't listed in the API schema because they're systematically excluded — they do exist, use them normally.
  • NavigationHost is in Sandbox.UI.Navigation, needs @using Sandbox.UI.Navigation in your Razor file.

Verification Loop (When In Doubt)

If you're about to write an API call and you're not sure it exists:

  1. Check the topical file for the area first (networking.md, ui-razor.md, etc.). Topical files include inline signatures for the APIs they cover.
  2. If not there, check api-schema-core.md. It has full signatures for the ~50 most-used types.
  3. If not there, check api-schema-extended.md. It's a namespace-organized index of 738 more types — find the type, then look up full signatures elsewhere if needed.
  4. If a method or property is in NONE of the above, it does not exist. Do not write it. Revisit your design — there is almost certainly an s&box-idiomatic way to do what you want.

The schema under raw/api-schema.json is not shipped with the skill and is not for you to read directly; the reference files are the curated, searchable view of it.


This file is a router. Reference files teach. Do not answer s&box questions from SKILL.md alone — open the relevant reference and read it.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

Report a problem with this skill