s&box API Reference with Verified Patterns

Living document of s&box API patterns confirmed through debugging. Always check here before implementing.

## Required Global Imports (Assembly.cs)

s&box projects need specific `global using` directives in `Code/Assembly.cs`. Without these, features silently fail or produce confusing compile errors.

```csharp
global using Sandbox; // Core — GameObjects, Components, Model, Log, etc.
global using Sandbox.Citizen; // CitizenAnimationHelper (not included by default)
global using System; // Exception, MathF, Math, Action, Func
global using System.Collections.Generic; // List<T>, Dictionary<K,V>
global using System.Linq; // .FirstOrDefault(), .Where(), .Select(), etc.
global using System.Threading.Tasks; // async Task return types (async void works without it, Task does not)
```

**Common symptoms of missing imports:**
| Missing import | What breaks |
|---|---|
| `Sandbox.Citizen` | `CitizenAnimationHelper` not found |
| `System` | `MathF` not found, `Exception` not found in catch blocks |
| `System.Threading.Tasks` | `async Task` methods won't compile (but `async void` still works) |
| `System.Linq` | `.FirstOrDefault()`, `.Where()` not available on collections |

**Note:** The `Editor/Assembly.cs` is separate and only applies to editor-time code.

---

## GameObject

### Creation
```csharp
var obj = new GameObject( true, "Name" );
```
- First param `true` enables the object on creation.

### Destruction
```csharp
obj.Destroy(); // immediate
obj.IsValid() // check if still alive before acting on it
```
- **NO** `DestroyAsync(float)` method exists.
- For delayed destroy, use async:
```csharp
await Task.DelayRealtimeSeconds( 3f );
if ( obj.IsValid() ) obj.Destroy();
```

### Hierarchy
```csharp
child.Parent = parentObj; // set parent
child.LocalPosition = Vector3.Zero; // position relative to parent
```

### Tags
```csharp
obj.Tags.Add( "player" );
```

---

## PlayerController (Built-in — PREFERRED)

Higher-level character controller with proper jumping, ground detection, stepping, and physics.
Use this instead of raw `CharacterController` for player movement.

**IMPORTANT:** Our custom class is named `MoonPlayerController` to avoid conflict.

### Key Properties
| Property | Type | Notes |
|----------|------|-------|
| `Velocity` | Vector3 | Current physical velocity minus ground velocity (**read-only**) |
| `GroundVelocity` | Vector3 | Velocity of the ground underneath |
| `IsAirborne` | bool | Not touching ground, not swimming/climbing |
| `IsDucking` | bool | Currently ducked |
| `GroundObject` | GameObject | What we're standing on (null if nothing) |
| `TimeSinceGrounded` | float | Time since last on ground |
| `TimeSinceUngrounded` | float | Time since last off ground |
| `WishVelocity` | Vector3 | Desired movement velocity |
| `EyeAngles` | Angles | Looking direction |
| `EyePosition` | Vector3 | Eye position in world |
| `AccelerationTime` | float | Seconds to reach target speed (accelerating) |
| `DeaccelerationTime` | float | Seconds to reach target speed (decelerating) |
| `BrakePower` | float | Extra friction when stopping |
| `AirFriction` | float | Friction while airborne |
| `Headroom` | float | Distance to ceiling |

### Key Methods
```csharp
pc.Jump( Vector3.Up * force ); // proper jump — subtracts opposite velocity first
pc.TryStep( stepHeight ); // step up terrain/stairs
pc.UpdateAnimation( renderer ); // built-in animation update
pc.UpdateDucking( wantsDuck ); // handle ducking
```

### Movement pattern
```csharp
pc.WishVelocity = wishDir * speed;

if ( Input.Pressed( "jump" ) )
pc.Jump( Vector3.Up * jumpForce );
```
PlayerController handles acceleration, friction, gravity, and Move() internally.

---

## CharacterController (Low-level)

Raw physics character capsule. Use `PlayerController` instead for players.

### Properties
| Property | Type | Notes |
|----------|------|-------|
| `Height` | float | Total capsule height |
| `Radius` | float | Capsule radius |
| `IsOnGround` | bool | Ground check (possibly read-only) |
| `Velocity` | Vector3 | Current velocity |
| `Bounciness` | float | Bounce off walls |

### Properties that DO NOT exist
- ~~`Bounce`~~ — use `Bounciness` instead
- ~~`StepHeight`~~ — may not exist, use PlayerController.TryStep instead
- ~~`GroundAngle`~~ — may not exist on CharacterController

### Methods
```csharp
cc.Accelerate( wishVelocity );
cc.ApplyFriction( friction, damping );
cc.Punch( Vector3.Up * force ); // disconnect from ground + add velocity
cc.Move();
cc.MoveTo( position, useTracing );
cc.TraceDirection( delta );
```

---

## Networking

### Player spawning pattern
Implement `Component.INetworkListener` on a component in the scene:
```csharp
public sealed class Spawner : Component, Component.INetworkListener
{
public void OnActive( Connection connection )
{
var player = new GameObject( true, "Player" );
// ... add components ...
player.NetworkSpawn( connection );
}
}
```
- Requires a `NetworkHelper` component on a GameObject in the scene.
- `OnActive` fires on the host for each connecting player (including host).

### NetworkHelper spawn points
```csharp
var networkHelper = Scene.GetAllComponents<NetworkHelper>().FirstOrDefault();
if ( networkHelper?.SpawnPoints?.Count > 0 )
{
var point = Game.Random.FromList( networkHelper.SpawnPoints );
spawnPos = point.WorldPosition;
}
```
- `NetworkHelper.SpawnPoints` is a `List<GameObject>` — drag GameObjects into it in the editor.
- Use this instead of hardcoding spawn positions.

### Sync properties
```csharp
[Sync] public Angles EyeAngles { get; set; }
[Sync] public string ClothingData { get; set; }
```

### Proxy check
```csharp
if ( IsProxy ) return; // skip input/camera for remote players
```

### Connection data
```csharp
connection.DisplayName // player name
connection.GetUserData( "avatar" ) // clothing/avatar JSON string
```

### NetworkSpawn and plain properties
- **CRITICAL:** `NetworkSpawn()` does NOT preserve plain C# properties (non-`[Sync]`, non-`[Property]`). If you set `component.Owner = someRef` and then call `NetworkSpawn()`, the `Owner` reference may be wiped.
- **Fix:** Either skip `NetworkSpawn` for local-only objects, or re-apply the property every frame from the spawning component.
- For equipment/attachments that only need to exist visually: skip `NetworkSpawn` entirely and keep them as local objects. Re-set the `Owner` reference from the parent component each frame as a safety net.

---

## CitizenAnimationHelper

**Namespace:** `Sandbox.Citizen` — must add `global using Sandbox.Citizen;` to Assembly.cs.

### Setup
```csharp
var animHelper = obj.Components.Create<CitizenAnimationHelper>();
animHelper.Target = skinnedModelRenderer; // REQUIRED or NullReferenceException
```

### Usage
```csharp
anim.WithVelocity( cc.Velocity );
anim.WithWishVelocity( wishVelocity );
anim.IsGrounded = cc.IsOnGround;
anim.MoveStyle = CitizenAnimationHelper.MoveStyles.Run; // or Walk
anim.WithLook( direction );
anim.TriggerJump();
```

---

## Clothing / Avatar

```csharp
var clothing = new ClothingContainer();
clothing.Deserialize( clothingJsonString );
clothing.PrefersHuman = true;
clothing.Apply( skinnedModelRenderer );
```
- Get clothing data from connection: `connection.GetUserData( "avatar" )`
- Apply creates child renderers for each clothing piece.
- **CRITICAL:** Always set `clothing.PrefersHuman = true` before `Apply()` — ensures the human citizen model is used instead of potentially falling back to a non-human variant.

### Citizen Model Validation (ERROR MODEL fix)

In production, citizen models can fail to load — resulting in the "ERROR MODEL" placeholder. This happens most often on proxy clients when clothing sync arrives late or the citizen addon isn't ready.

**Detection:** Real citizen models have bone attachments (e.g., "head"); the error model does not:
```csharp
private bool BodyHasRealModel()
{
if ( Body is null || Body.Model is null ) return false;
try
{
var attach = Body.GetAttachment( "head" );
return attach.HasValue;
}
catch { return false; }
}
```

**Recovery pattern:** Check 2 seconds after spawn (bones need time to initialize), retry up to 3 times with fallback model paths:
```csharp
string[] fallbacks = new[]
{
"models/citizen_human/citizen_human_male.vmdl",
"models/citizen_human/citizen_human_female.vmdl",
"models/citizen/citizen.vmdl",
};

foreach ( var path in fallbacks )
{
Body.Model = Model.Load( path );
// Re-apply clothing on the new base model
var clothing = new ClothingContainer();
clothing.Deserialize( ClothingData );
clothing.PrefersHuman = true;
clothing.Apply( Body );
break; // on success
}
```

**Key details:**
- Validation must run for ALL players (including proxies) — put the check BEFORE `if (IsProxy) return;`
- Cache the `SkinnedModelRenderer` as a `Body` property instead of searching the hierarchy each time
- Use a deferred timer (2s) in `OnUpdate()` — checking in `OnStart()` is too early for bone attachments

---

## Models

### Loading
```csharp
renderer.Model = Model.Load( "models/citizen/citizen.vmdl" );
```
- Main citizen model (with clothing support): `models/citizen/citizen.vmdl`
- Male body only: `models/citizen_human/citizen_human_male.vmdl`
- Female body only: `models/citizen_human/citizen_human_female.vmdl`
- ~~`models/citizen_human.vmdl`~~ — does NOT exist, will fail to load

---

## Input

### Reading input
```csharp
Input.AnalogMove // Vector3, WASD movement
Input.AnalogLook // Angles, mouse delta
Input.Down( "run" ) // held down
Input.Pressed( "jump" ) // just pressed this frame
Input.MouseWheel // Vector2, scroll wheel (.y for vertical)
```

### Input config
- Defined in `ProjectSettings/Input.config`
- Action names are case-sensitive
- Always check what key is actually bound before using an action name
- Key bindings: `"alt"`, `"shift"`, `"ctrl"`, `"space"`, `"mouse1"`, etc.

---

## Physics / Gravity

- **Cannot be set in scene inspector** (only TimeScale is visible).
- Set via code:
```csharp
Scene.PhysicsWorld.Gravity = new Vector3( 0, 0, -400f );
```
- Default Earth gravity: `0, 0, -800`
- Moon gravity suggestion: `0, 0, -400`

---

## Scene Tracing

```csharp
var tr = Scene.Trace.Ray( start, end )
.WithoutTags( "player" )
.Radius( 8f )
.Run();

tr.Hit // bool
tr.HitPosition // Vector3
tr.Normal // Vector3
tr.Distance // float — distance from start to hit
```

### Ignoring specific objects
```csharp
.IgnoreGameObjectHierarchy( GameObject ) // skip this object + all children
.WithoutTags( "player", "phaser" ) // skip anything with these tags
```
- **CRITICAL:** When tracing from a vehicle, ALWAYS exclude the player inside it. Hover traces will hit the player's collider sitting inside the vehicle, returning near-zero distance and triggering emergency forces (car launches into space).
- Tag everything that shouldn't be hit: `"player"`, `"phaser"`, `"vehicle"`, any debug objects.
- `IgnoreGameObjectHierarchy` only ignores the specified hierarchy — standalone GameObjects (debug lights, effects) need tag-based exclusion.

### Flatten direction for horizontal traces
```csharp
var forward = WorldRotation.Forward.WithZ( 0 ).Normal; // horizontal only
```
- When casting look-ahead rays for terrain anticipation, flatten the forward vector. Otherwise a pitched vehicle sends rays into the sky/ground instead of ahead.

---

## Particle System

Uses prefab-based particles, NOT code-based. Key components:

| Component | Role |
|-----------|------|
| `ParticleEffect` | Main controller (alpha, scale, lifetime, forces, color) |
| `ParticleSphereEmitter` | Emits from sphere (Rate, RateOverDistance, Burst) |
| `ParticleConeEmitter` | Emits in cone shape |
| `ParticleSpriteRenderer` | Renders as billboarded sprites |

### Prefab loading and spawning
```csharp
var prefab = ResourceLibrary.Get<PrefabFile>( "effects/moon_dust_trail.prefab" );
var obj = SceneUtility.GetPrefabScene( prefab ).Clone();
obj.WorldPosition = position;
```
- Prefab files are JSON in `Assets/` directory
- Path is relative to Assets folder
- Smoke texture: `textures/particles/smoke/smoke_loop_a.vtex`

### Key emitter properties
- `Rate` — particles per second (continuous)
- `RateOverDistance` — particles per unit moved (trails)
- `Burst` — one-time particle count
- `Loop` — repeat emission
- `DestroyOnEnd` — auto-destroy emitter when done
- `Velocity` — outward velocity from emitter shape

---

## Component Lookup

```csharp
Components.Get<T>() // same GameObject only
Components.GetInDescendantsOrSelf<T>() // this + all children
Components.GetInAncestorsOrSelf<T>() // this + all parents
```
- Use `GetInDescendantsOrSelf` when components are on child objects (e.g., model on child).

---

## Camera

```csharp
var camera = Scene.Camera; // get the active scene camera
camera.WorldPosition = pos;
camera.WorldRotation = rot;
```

---

## Lifecycle Methods

```csharp
protected override void OnStart() // called once when component starts
protected override void OnUpdate() // called every frame
protected override void OnFixedUpdate() // called on physics tick
```

---

## Particle Prefab Gotchas

- `ParticleGradient` Evaluation field only accepts: `"Particle"`, `"Life"` — NOT `"Seed"`
- `ParticleFloat` Evaluation accepts: `"Seed"`, `"Life"`, `"Frame"`, `"Particle"`
- For one-shot burst effects: set `Burst` count, `Loop: false`, `DestroyOnEnd: true`, `Duration: 0.1`
- `RateOverDistance` only works if the emitter GameObject is physically moving — don't use for cloned-per-event puffs

### CRITICAL: How to spawn particle prefabs correctly

**The ONLY pattern that works:**
1. Load the PrefabFile ONCE in `OnStart()` and store it
2. Clone from the stored reference when needed

```csharp
// In OnStart — load once
private PrefabFile myPrefab;
protected override void OnStart()
{
myPrefab = ResourceLibrary.Get<PrefabFile>( "effects/my_effect.prefab" );
}

// When spawning — use stored reference
var puff = SceneUtility.GetPrefabScene( myPrefab ).Clone();
puff.WorldPosition = position;
```

**What does NOT work:**
- ~~Loading `ResourceLibrary.Get<PrefabFile>()` inline every frame~~ — may return null outside OnStart
- ~~Creating ParticleEffect/ParticleSpriteRenderer/ParticleSphereEmitter from code~~ — the property types (ParticleFloat, ParticleVector3, ParticleGradient) are complex serialized types, not simple floats. Setting them via code (e.g., `effect.Tint = color`) may not work as expected.
- ~~Using a different prefab path than what was loaded in OnStart~~ — always use the stored PrefabFile

**Scaling cloned particles:** Use `puff.WorldScale = Vector3.One * scale` to make particles bigger/smaller.

**Cleanup:** Use async destroy pattern:
```csharp
async Task DestroyAfter( GameObject obj, float seconds )
{
await Task.DelayRealtimeSeconds( seconds );
if ( obj.IsValid() ) obj.Destroy();
}
```

---

## Razor UI

```csharp
@using Sandbox
@inherits PanelComponent
@namespace MoonHaulers.UI
```
- Razor components inherit from `PanelComponent`
- Show/hide with CSS classes and `BuildHash()` for re-renders
- **Cannot** add directly to a GameObject — need a `ScreenPanel` component as host:
```csharp
var hudObj = new GameObject( true, "HUD" );
hudObj.Components.Create<ScreenPanel>();
hudObj.Components.Create<MoonHaulers.UI.MyPanel>();
```
- Reference with full namespace if the Razor uses `@namespace`
- Mouse cursor: ~~`Mouse.Visible`~~ is **obsolete** — avoid using it
- Stylesheets: `.razor.scss` files alongside `.razor` files
- **DO NOT** use `@import` for shared styles in `.razor.scss` — it fails at runtime with "Missing import" errors. Instead, hardcode color values directly (matching the global palette). Existing components all hardcode values.
- `BuildHash()`: use incremental `System.HashCode` pattern, NOT `System.HashCode.Combine()` with many args — the 8-arg overload causes "Unable to find matching substitution for a static method" in s&box Razor compiler:
```csharp
// CORRECT — works in s&box Razor
protected override int BuildHash()
{
var hash = new System.HashCode();
hash.Add( Value1 );
hash.Add( Value2 );
return hash.ToHashCode();
}

// BROKEN — System.HashCode.Combine with 8 args crashes
// protected override int BuildHash() => System.HashCode.Combine(a,b,c,d,e,f,g,h);
```

---

## Rigidbody

```csharp
var rb = Components.Get<Rigidbody>();
rb.Velocity // get/set velocity
rb.AngularVelocity // get/set angular velocity
rb.Mass // read mass
rb.ApplyForce( force ); // apply force
rb.ApplyForceAt( position, force ); // apply force at world position
rb.ApplyTorque( torque ); // apply rotational force
```

---

## Lights

```csharp
// Spot light (headlights)
var light = obj.Components.Create<SpotLight>();
light.LightColor = Color.White; // NOT .Color — use .LightColor
light.Radius = 1500f;
light.ConeInner = 15f;
light.ConeOuter = 35f;

// Point light (rear lights)
var light = obj.Components.Create<PointLight>();
light.LightColor = new Color( 1f, 0.1f, 0.05f ); // NOT .Color
light.Radius = 200f;
```
- ~~`.Color`~~ — does NOT exist on SpotLight/PointLight. Use `.LightColor`

---

## Gizmos (Editor Visualization)

Override `DrawGizmos()` on any Component to draw in the editor:
```csharp
protected override void DrawGizmos()
{
Gizmo.Draw.Color = Color.Cyan;
Gizmo.Draw.LineSphere( localPos, 5f ); // wireframe sphere
Gizmo.Draw.Arrow( from, to, 2f, 4f ); // directional arrow
Gizmo.Draw.Line( a, b ); // simple line
Gizmo.Draw.SolidSphere( pos, radius, 8, 8 ); // filled sphere
Gizmo.Draw.LineBBox( bbox ); // wireframe box
Gizmo.Draw.Text( "label", transform ); // 3D text
}
```
- Positions are in **local space** (relative to the component's GameObject)
- Set `Gizmo.Draw.Color` before each draw call
- Use `Gizmo.Draw.IgnoreDepth = true` to draw on top of everything
- **EDITOR ONLY** — `DrawGizmos()` does NOT render in-game/play mode. For runtime debug visuals, create actual GameObjects with `PointLight` components (guaranteed visible). `models/dev/sphere.vmdl` and `models/dev/box.vmdl` may not exist or may not load.

### Runtime Debug Visuals
Since gizmos and dev models are unreliable at runtime, use **PointLight** for debug:
```csharp
var debugObj = new GameObject( true, "Debug" );
debugObj.Tags.Add( "debug_tag" ); // so traces can ignore it
var light = debugObj.Components.Create<PointLight>();
light.LightColor = Color.Red;
light.Radius = 150f; // use 100+ for visibility at distance
```
- Color-code by state: Green = OK, Yellow = warning, Red = problem
- Tag debug objects so physics traces skip them
- Clean up in `OnDestroy()`

---

## Runtime Line/Beam Rendering

**CONFIRMED: `Gizmo.Draw` works at runtime inside `OnUpdate()`.** This is the best way to draw beams, lines, and particles. Verified from wizard_draw project (lightning beam, black hole effect).

**Available runtime Gizmo.Draw methods:**
```csharp
Gizmo.Draw.Color = new Color( r, g, b, alpha ); // set color + alpha
Gizmo.Draw.Line( from, to ); // visible line in air
Gizmo.Draw.SolidSphere( pos, radius ); // filled sphere (glow, spark)
Gizmo.Draw.LineCircle( center, normal, radius ); // wireframe circle
Gizmo.Draw.LineSphere( pos, radius ); // wireframe sphere
```

**Beam pattern (segmented with wave displacement):**
```csharp
float time = Time.Now;
int segments = 15;
var prev = emitPos;
for ( int i = 1; i <= segments; i++ )
{
float t = (float)i / segments;
var point = Vector3.Lerp( emitPos, hitPos, t );
if ( i < segments )
{
point += new Vector3(
MathF.Sin( time * 25f + i * 3.7f ) * jitter,
MathF.Cos( time * 22f + i * 2.3f ) * jitter,
MathF.Sin( time * 18f + i * 5.1f ) * jitter * 0.7f
);
}
Gizmo.Draw.Color = new Color( 0.6f, 0.8f, 1f, 0.8f );
Gizmo.Draw.Line( prev, point );
Gizmo.Draw.Color = new Color( 0.4f, 0.6f, 1f, 0.3f );
Gizmo.Draw.SolidSphere( point, 2f );
prev = point;
}
```

**Key notes:**
- PointLights/SpotLights only illuminate surfaces — NOT visible in empty air
- `Gizmo.Draw.Line` IS visible in air (immediate-mode rendering)
- Use `Gizmo.Draw` in `OnUpdate()` for runtime effects, `DrawGizmos()` for editor-only
- See `docs/references/` for full lightning beam and black hole effect implementations

---

## Common Gotchas

1. **CitizenAnimationHelper needs Target set** — will NullRef on WithVelocity without it
2. **No `Bounce` on CharacterController** — use `Bounciness` instead
3. **No `DestroyAsync`** — use async + Task.DelayRealtimeSeconds
4. **Input action names must match Input.config** — check actual bindings, don't assume
5. **`Sandbox.Citizen` namespace** — not included by default, add to Assembly.cs
6. **Scene gravity not in inspector** — must set via `Scene.PhysicsWorld.Gravity` in code
7. **Feet clipping** — put model renderer on child object with small upward offset (~4 units)
8. **Light color** — use `.LightColor` not `.Color` on SpotLight/PointLight
9. **Razor PanelComponents** — need a `ScreenPanel` host, can't be added to bare GameObjects
10. **HashCode in Razor** — do NOT use `System.HashCode.Combine()` with many args. Use incremental `var hash = new System.HashCode(); hash.Add(...); return hash.ToHashCode();`
11. **s&box CSS gradients** — only support percent stops, NOT degrees (use `50%` not `180deg`)
12. **`MathF`** — needs `global using System;` in Assembly.cs
13. **Built-in PlayerController** — don't disable/enable it, it doesn't resume properly. Use a flag like `IsDriving` to gate input instead. `Velocity` is **read-only** — use `WishVelocity` to control movement
14. **Particle prefabs** — MUST load PrefabFile in OnStart and store it. Loading inline each frame fails. Creating ParticleEffect from code doesn't work (complex serialized property types)
15. **Input.AnalogMove axes** — `.x` = forward/backward (W/S), `.y` = left/right (A/D). NOT the other way around
16. **Rigidbody forces** — `rb.ApplyForce()` may not work reliably. Use `rb.Velocity +=` directly for more predictable results
17. **`View` input action** — reserved by built-in PlayerController (toggles model visibility). Don't bind to it — create custom actions like `"FreeLook"` instead
18. **Transform is a struct** — storing `child.WorldTransform` captures a snapshot. Store the `GameObject` reference instead and read `.WorldPosition` each frame
19. **Speed conversion** — 1 s&box unit = 1 inch. To convert units/s to km/h: multiply by `0.09144`
20. **Components.Get<IInterface>()** — doesn't work for interfaces. Use `Components.GetAll<Component>()` then cast: `if (comp is IMyInterface impl)`
21. **Razor .scss @import fails at runtime** — `@import "/Code/UI/Styles/global.razor.scss"` causes "Missing import" errors. Hardcode color values instead (all existing components do this).
22. **Mouse.Visible is obsolete** — avoid using it, find alternative cursor management
23. **`System.Threading.Tasks`** — must add `global using System.Threading.Tasks;` to Assembly.cs for `async Task` return types. `async void` works without it but `Task` return type does not.
24. **NetworkSpawn wipes plain properties** — any non-`[Sync]`/non-`[Property]` field set before `NetworkSpawn()` may be lost. Either skip NetworkSpawn for local objects or re-apply the reference every frame.
25. **Hover vehicle traces MUST exclude players** — when a player enters a vehicle, their collider is still inside. Hover traces hit it at near-zero distance, triggering emergency forces that launch the car. Always add `.WithoutTags("player")` to vehicle hover traces.
26. **TimeSince defaults can cause issues** — `TimeSince` starts counting from component creation. A "stuck detection" timer that checks `timeSinceMoving > threshold` will fire immediately if the object was idle since spawn. Reset timers when state changes (e.g., when a player enters a vehicle).
27. **No LineRenderer in s&box** — there is no LineRenderer, SceneLineObject, or runtime line drawing API. Use PointLight chains for visible beams. Gizmo.Draw.Line is editor-only.
28. **dev models may not exist** — `models/dev/sphere.vmdl` and `models/dev/box.vmdl` may not be available. ModelRenderer will silently fail to render. Use PointLight for guaranteed runtime visibility.
29. **Over-the-shoulder camera** — add `cameraRot.Right * offset` to the camera target position for third-person games where the aim point would otherwise be blocked by the player model.
30. **`HighlightOutline` may not exist** — `HighlightOutline` component is NOT confirmed to exist in s&box. Do not use it. For highlighting objects, use a `PointLight` glow on a child GameObject and toggle `.Enabled`. Need to verify the correct s&box highlight component name.
31. **`SceneTraceResult.GameObject` may not exist** — the trace result struct (`tr.Hit`, `tr.HitPosition`, etc.) may NOT have a `.GameObject` property. Do not rely on it. Instead, find nearby components using `Scene.GetAllComponents<T>()` and compare positions to `tr.HitPosition`.
32. **s&box CSS `border-style` not supported** — `border-style: solid` causes runtime errors. Use shorthand `border-top: 2px #color` instead of separate `border-style`/`border-width`/`border-color` properties.
33. **`SolidCylinder` has no `sections` param** — `Gizmo.Draw.SolidCylinder(from, to, radius)` takes only 3 args. No `sections` parameter.
30. **Citizen model ERROR MODEL in production** — citizen models can silently fail to load, showing the error placeholder instead. Detect by checking `Body.GetAttachment("head")` (error model has no bone attachments). Use deferred validation (2s after spawn) with fallback model paths and `PrefersHuman = true` on ClothingContainer. Must run for proxies too — they fail most often.
31. **ClothingContainer.PrefersHuman** — always set `PrefersHuman = true` before `Apply()`. Without it, the clothing system may load a non-human model variant that fails silently.
32. **`Color.Parse()` does not exist** — s&box `Color` struct has no `Parse` or `TryParse` method. To convert hex strings to Color, manually parse the hex bytes: `new Color( r / 255f, g / 255f, b / 255f )`. Use `int.TryParse(hex, NumberStyles.HexNumber, ...)` for each component.

---

## Procedural Mesh Creation (UNVERIFIED — needs compile test)

Pattern for creating procedural meshes at runtime:

```csharp
var material = Material.Load( "materials/my_material.vmat" );
var mesh = new Mesh( material );
mesh.CreateVertexBuffer<Vertex>( verts.Length, Vertex.Layout, verts );
mesh.CreateIndexBuffer( indices.Length, indices );
mesh.Bounds = new BBox( min, max );

var model = Model.Builder
.AddMesh( mesh )
.AddCollisionMesh( positionsArray, indicesArray ) // for physics collision
.Create();

var renderer = obj.Components.Create<ModelRenderer>();
renderer.Model = model;
```

**Vertex struct:** `Sandbox.Vertex` — constructor: `new Vertex( Vector3 position, Vector3 normal, Vector3 tangent, Vector4 texcoord )`
- `Vertex.Layout` is the static vertex attribute layout
- texcoord is Vector4: xy = UV0, zw = UV1

**ModelCollider:** Can also be used alongside ModelRenderer for collision from a procedural model:
```csharp
var collider = obj.Components.Create<ModelCollider>();
collider.Model = model;
```

**Status:** This API is used in `Code/Terrain/ProceduralTerrainManager.cs` but hasn't been compile-verified yet. If `Vertex`, `Mesh`, or `Model.Builder` APIs differ, update this section.

## 32. HTTP Requests (Network Storage / External APIs)

**Http.RequestStringAsync** — built-in s&box static helper for HTTP requests:

```csharp
// GET request
var result = await Http.RequestStringAsync( url );

// POST request with JSON body
var body = JsonSerializer.Serialize( new { key = "value" } );
var content = new StringContent( body, Encoding.UTF8, "application/json" );
var result = await Http.RequestStringAsync( url, "POST", content );
```

**Required usings for JSON + HTTP:**
```csharp
using System.Text;
using System.Text.Json;
using System.Net.Http; // for StringContent
```

**s&box Auth Token** — verify player identity with external services:
```csharp
var token = await Services.Auth.GetToken( "sbox-network-storage" );
```
- Returns a signed token that the backend verifies via Facepunch services
- Service name for Network Storage: `"sbox-network-storage"`
- With `global using Sandbox;`, use `Services.Auth` (no prefix needed)

**Game.SteamId** — get the local player's Steam ID:
```csharp
var steamId = Game.SteamId.ToString(); // Returns 17-digit string like "76561198..."
```

**Pattern: Auth as query params** — s&box `Http` API doesn't easily support custom headers, so auth goes in query string:
```csharp
var url = $"https://storage.sbox.cool/v3/api/endpoints/{projectId}/{slug}";
url += $"?apiKey={apiKey}&steamId={steamId}&token={token}";
```

**Pattern: Parse JSON response with JsonElement:**
```csharp
var json = JsonSerializer.Deserialize<JsonElement>( result );
if ( json.TryGetProperty( "groups", out var groups ) )
{
int value = groups.GetProperty( "combat" ).GetProperty( "xp_per_kill" ).GetInt32();
}
```

**Gotcha:** `Http.RequestStringAsync` returns the raw response body as a string. If the server returns an error HTTP status, the method may throw or return an error string — always wrap in try/catch.

**Player display name:** Use `Connection.Local?.DisplayName` (NOT `Game.UserName` which doesn't exist). For a specific connection: `connection.DisplayName`.

---

## Filesystem APIs — CRITICAL

s&box has multiple filesystem roots that point to DIFFERENT directories:

| API | Points To | Example Path |
|-----|-----------|-------------|
| `Editor.FileSystem.Root` | **s&box engine install directory** | `C:\Program Files (x86)\Steam\steamapps\common\sbox` |
| `Sandbox.FileSystem.Mounted` | **Project `assets/` folder** | `C:\users\user\documents\s&box projects\moon_haulers\assets` |
| `Sandbox.FileSystem.Data` | **s&box data directory** | `C:\...\sbox\data\bug\moon_haulers#local` |
| `Environment.CurrentDirectory` | **s&box engine directory** | Same as `Editor.FileSystem.Root` |

**DO NOT use `Editor.FileSystem.Root` for project files.** It resolves to the engine directory, not your project. Files read from there will be stale/wrong.

**DO NOT use `Sandbox.FileSystem.Mounted` with `../` paths.** It's sandboxed and throws `ArgumentException` on parent traversal.

**Correct approach for project file access:**
```csharp
// Derive project root from Mounted (which is project/assets)
var assetsPath = Sandbox.FileSystem.Mounted.GetFullPath( "" );
var projectRoot = System.IO.Path.GetDirectoryName( assetsPath );

// Then use System.IO with absolute paths
var filePath = System.IO.Path.Combine( projectRoot, "Editor/Network Storage/workflows/check-has-ore.json" );
var content = System.IO.File.ReadAllText( filePath );
```

**`Editor.FileSystem.Root.GetFullPath()`** also returns engine-relative paths, not project paths. Do not use it.