# Technical Documentation: Facepunch/sbox-public

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/Facepunch/sbox-public)

### Overview (`README.md`)

<div align="center">
  <img src="https://sbox.game/img/sbox-logo-square.svg" width="80px" alt="s&box logo">

  [Website] | [Getting Started] | [Forums] | [Documentation] | [Contributing]
</div>

[Website]: https://sbox.game/
[Getting Started]: https://sbox.game/learn/getting-started
[Forums]: https://sbox.game/f/
[Documentation]: https://sbox.game/dev/doc/
[Contributing]: CONTRIBUTING.md

# s&box

[s&box](https://sbox.game) is a modern game engine, built on Valve's Source 2 and the latest .NET technology, it provides a modern intuitive editor for creating games.



If your goal is to create games using s&box, please start with [getting started guide](https://sbox.game/learn/getting-started).
This repository is for building the engine from source for those who want to contribute to the development of the engine.

## Getting the engine

### Steam

You can download and install the s&box editor directly from [Steam](https://store.steampowered.com/app/590830/sbox/).

### Compiling from source

This repository contains the C# engine, editor, tooling and game content. The native Source 2 core is
distributed as prebuilt binaries that setup downloads for your platform, so no C++ toolchain is needed.

| Platform | Setup | Notes |
|----------|-------|-------|
| Windows 10 / 11 (x64) | `Setup.bat` | |
| Linux (x64) | `./Setup.sh` | Binaries target the Steam Linux Runtime, most distros should work. |
| macOS (Apple Silicon) | `./Setup.sh` | Intel Macs are not supported. |

#### Prerequisites

* [Git](https://git-scm.com/downloads)
* [.NET 10 SDK](https://dotnet.microsoft.com/en-us/download)
* An IDE for the C# code is recommended: [Visual Studio 2026](https://visualstudio.microsoft.com/) or
  [Rider](https://www.jetbrains.com/rider/) on Windows, Rider or [VS Code](https://code.visualstudio.com/) on Linux and macOS.

#### Setup

```bash
# Clone the repo
git clone https://github.com/Facepunch/sbox-public.git
cd sbox-public

# Windows
Setup.bat

# Linux / macOS
./Setup.sh
```

Once setup completes, the game (sbox) and editor (sbox-dev.exe) run from the `game` folder.

#### Staying up to date

Pulling on Git will run a hook that fetches any new native binaries or content that changed and regenerates the interop bindings. Build the C# code from your IDE as usual, or rerun `Setup.bat` for a full incremental rebuild including shaders and content.

## Contributing

If you would like to contribute to the engine, please see the [contributing guide](CONTRIBUTING.md).

If you want to report bugs or request new features, see [sbox-issues](https://github.com/Facepunch/sbox-public/issues/).

## Documentation

Full documentation, tutorials, and API references are available at [sbox.game/dev/](https://sbox.game/dev/).

## License

The s&box engine source code is licensed under the [MIT License](LICENSE.md).

Certain native binaries in `game/bin` are not covered by the MIT license. These binaries are distributed under the s&box EULA. You must agree to the terms of the EULA to use them.

This project includes third-party components that are separately licensed.
Those components are not covered by the MIT license above and remain subject
to their original licenses as indicated in `game/thirdpartylegalnotices`.

---

### Overview (`engine/Sandbox.CodeUpgrader/Readme.md`)

# Analyzer

Analyzers create diagostrics. The analyzers look at the code for stuff and if it finds
it, it creates a diagnostic.

# Diagnostic

A diagnostic is one of those CS234 things you get in the output in your visual 
studio console. We want them to all have unique numbers so we define them in 
a big static class called Diagnostics, and reference them from there.

# Fixer

The fixers respond to the diagnostics, and use roslyn to convert one peice of
code to a different peice of code.

# Tests

Each Analyzer and Fixer has its own tests. To make things simpler the tests 
are in the class itself.

The tests aren't automatically collected, you need to add them to the test 
project ( in UpgraderTests.cs )

# Test markup

In the test code you're going to see this weird [| and |] markup.

This is telling the tester that we expect a diagnostic between these two tokens.

For example if we have this code:

```
class [|Type1|] { }
```

We're telling the test that we're expecting a diagnostic to trigger for the 
name Type1 for some reason. If it does create a single diagnostic for that span
then the test will pass.

If you do this

```
class Type1 { }
```

and no diagnostics are created then it will pass.

more: https://www.meziantou.net/how-to-test-a-roslyn-analyzer.htm#marker-syntax

---

### Overview (`engine/Sandbox.SceneRenderer/README.md`)

# Sandbox.SceneRenderer

A C# scene renderer for s&box, intended to replace the native scenesystem's world rendering.
It manages render objects, visibility, lighting and frame execution while using the engine's existing
materials, shaders and graphics backend.

It also holds the layers of native's rendering pipeline ([Native](Native/)): native sets each view up and
this adds its layers, for every camera that isn't rendered here. See the
[render pipeline doc](../../docs/managed/render-pipeline.md).

The renderer is experimental. To use it for GameObject scenes, enable `r_managed_scene 1`.
It is disabled by default. [SceneLab](../Launcher/SceneLab/) runs the managed and native renderers
against the same scenes for visual comparison and performance testing.

This README covers the project structure and development workflow. The
[renderer reference](../../docs/managed/scene-renderer.md) contains the implementation details,
native equivalents, performance investigations and debugging history.

## What it renders

- Static and skinned meshes, with LOD selection, instancing, morphs and model deformers.
- Opaque, translucent and faded geometry, decals, glass, viewmodels and screen overlays.
- Clustered point and spot lights; directional, spot and point shadows; contact shadows.
- Environment maps, baked lighting, light probe volumes, 2D and 3D skies, and gradient, cubemap and volumetric fog.
- Custom objects such as particles, sprites, terrain, clutter, debug geometry and world panels.
- Camera command lists, post processing, auto exposure and UI through the existing camera stages.
- Render-to-texture targets, MSAA, tools materials and debug views.

The GameObject bridge mirrors scene changes into the renderer, including material overrides,
body groups, tags, per-object attributes and camera settings.

## Architecture

`RenderWorld` owns the scene data. `RenderView` describes a camera, and `RenderSystem` turns the visible
part of a world into a frame. Features prepare and draw different kinds of objects; layers specify the
steps of the frame. `FrameRecorder` decides which layers are needed and records their work for submission.

Rendering uses a dedicated native `IRenderContext`, rather than native `ISceneSystem`, `ISceneView` or
`ISceneLayer` objects. The renderer still relies on shared engine services, including the material
system, shader code, shadow-map policy and skinning vertex cache.

### Source layout

| Folder | Responsibility |
| --- | --- |
| [World](World/) | Scene foundations: `RenderWorld`, `RenderObject` and the `CustomObject` extension point. |
| [World/Objects](World/Objects/) | Meshes, lights, environment-map objects and decals, plus their `RenderMesh` data. |
| [World/Environment](World/Environment/) | Scene lighting, light probe volumes, fog settings and the 3D skybox. |
| [Culling](Culling/) | The dynamic spatial tree, frustum tests and screen-size culling. |
| [Pipeline](Pipeline/) | Cameras, frames, targets, collection, preparation and command recording. |
| [Pipeline/Features](Pipeline/Features/) | `RenderFeature`, mesh preparation and drawing, and clustered light/probe/decal binning. |
| [Pipeline/Layers](Pipeline/Layers/) | Render passes and their resource dependencies. |
| [Pipeline/Shadows](Pipeline/Shadows/) | Integration with the engine's shared `ShadowMapper`. |
| [Pipeline/Fog](Pipeline/Fog/) | Volumetric fog lighting, temporal accumulation and integration. |
| [Gpu](Gpu/) | Native rendering access, GPU uploads, transform buffers and shader constants. |
| [Bridge](Bridge/) | Synchronization with GameObject scenes and execution of existing camera stages. |
| [Diagnostics](Diagnostics/) | Frame comparison, rendering statistics and profiler instrumentation. |
| [Native](Native/) | Native's rendering pipeline: the layers of its standard and 3D skybox views (`NativeRenderPipeline`). |

Folders describe responsibilities; they do not all introduce namespaces. Public types use
`Sandbox.SceneRenderer`. Internal subsystems also use `.Features`, `.Culling`, `.Shadows`, `.Gpu`,
`.Bridge` and `.Native`; the first four are imported within the project by [Assembly.cs](Assembly.cs).

Shared math belongs in `Sandbox.System`. In particular,
[Matrix3x4](../Sandbox.System/Math/Matrix3x4.cs) lives alongside `Matrix` and represents the native affine
layout used for bones and transform buffers.

### How a frame runs

`RenderSystem.Render( world, view, target )` renders into a `ViewTarget`. The internal swap-chain
overload is used by the engine and SceneLab.

1. **Collect:** query the spatial tree, test exact bounds and screen size, then group visible objects by feature.
2. **Prepare:** choose LODs, sort draw runs, pack lights and write transform data. The shadow system requests
   additional views, which are culled and prepared in the same way.
3. **Setup:** create or resolve GPU resources, upload constants and buffers, and dispatch feature setup work.
4. **Draw:** record the required layers and submit them in order, resolving or copying the final output as needed.

Collect and Prepare are CPU-only. This keeps them testable without booting the native engine.

The layer order is defined in [RenderSystem.Layers.cs](Pipeline/RenderSystem.Layers.cs).
Layers declare the resources they read and write through `FrameResources`. Planning walks those
dependencies backwards and drops producers whose output is unused. `IsNeeded` handles whether a layer
can do useful work for the current frame.

Large frames record work in parallel. Each recording segment has its own native context, created on
the thread that records it. Shadow culling is also parallelized. Camera stages and custom-object work
that requires the main thread stay there. Small frames avoid the overhead of parallel recording.
With `r_managed_async_compute` (enabled by default), depth-chain, contact-shadow and AO work can overlap
shadow-map rendering. SSR automatically starts on graphics once Hi-Z is ready, alongside AO on compute.

Declare each pass's inputs, outputs and compute capability. The pipeline itself stays a plain list:

```csharp
// On the AO pass:
public override FrameResources Reads( RenderFrame frame ) => FrameResources.DepthChain | FrameResources.Normals;
public override FrameResources Writes => FrameResources.AmbientOcclusion;
public override bool SupportsAsyncCompute => true;

// In the pipeline, in serial execution order:
new DepthChainLayer(),
new EarlyStageLayer( Stage.AfterDepthPrepass, PostProcessQueue.AsyncCompute ),
contactShadows,
new EarlyStageLayer( Stage.AfterDepthPrepass, PostProcessQueue.Graphics ),
new ShadowLayer(),
new ShadowsReadyLayer(),
```

`FrameRecorder` finds consecutive compute-capable passes and the following independent graphics work. It
splits compute submissions at the results graphics needs and inserts waits for read/write, write/read and
write/write conflicts. Read-only sharing needs no wait. Graphics producers submit before the next compute
batch's handoff; outstanding compute joins before that handoff, another frame or untracked work. No groups,
named pass dependencies or manual semaphore wiring are required in the pipeline.
Reusing an earlier compute pass's input or output starts another batch, so graphics preparation cannot clear it too early.

`Reads` defaults to `FrameResources.Untracked`: an undeclared pass is a conservative scheduling boundary.
To allow overlap, override it with all resources the pass reads (`None` for no inputs), and declare its
outputs in `Writes`. Include `Untracked` alongside known inputs for arbitrary camera hooks or custom drawing.
These declarations also drive output culling; `HasSideEffects` keeps work such as the depth prepass even
when no later pass explicitly reads its output. This does not inspect shader bindings or allocate resources.

Each queue preserves list order. Disabled/unsupported async compute and windows with no independent graphics
work stay serial. CPU worker thresholds do not control GPU overlap. Graphics-only preparation and result
transitions remain in `HandOff` and `TakeBack`; preparation must only initialize outputs and transition inputs
that already exist before the batch. Early camera effects stay at their normal stage on the serial path.

### Connections to the rest of the engine

- [ManagedScene](../Sandbox.Engine/Systems/Render/ManagedScene/) owns the convars, scene-change notifications
  and shutdown integration. The app system registers this assembly with the engine at startup
  ([EngineHooks.cs](EngineHooks.cs)).
- [ShadowMapper](../Sandbox.Engine/Systems/Render/Shadows/) supplies shared shadow allocation, caching
  and time-slicing policy for both renderers.
- [Graphics.IFrameView](../Sandbox.Engine/Systems/Render/Graphics.FrameView.cs) lets camera effects and
  custom drawing operate on either renderer. Use the `Graphics` abstraction rather than assuming a
  native scene view or layer exists.
- [RenderTools.def](../Definitions/engine/Render/RenderTools.def) exposes native helpers implemented in
  [rendertools.cpp](../../src/engine2/sbox/rendertools.cpp).
- [Gpu/SharedPasses.cs](Gpu/SharedPasses.cs) holds the depth chain, quarter depth and bloom and refraction
  targets, which this renderer and native's pipeline both use.

## Building and testing

Run these commands from the repository root. Close SceneLab before building because it locks the
assemblies in `game/bin/managed`. See the [build guide](../../docs/architecture/build-system.md) for
initial setup and native build options.

```powershell
# Build managed code and regenerate bindings, using the existing native build.
dotnet run --project engine/Tools/SboxBuild/SboxBuild.csproj -- build --skip-native

# Run the renderer's unit tests after building.
dotnet test -c Release --no-build engine/Tests/Sandbox.Test.Unit --filter "FullyQualifiedName~SceneRendererTests"
```

The [unit tests](../Tests/Sandbox.Test.Unit/SceneRenderer/) cover culling, object storage, transforms,
GPU layouts, light packing, layer selection and allocation behavior. Tests that prepare frames use
`[DoNotParallelize]` because `ShadowMapper` and `Application.FrameCount` contain shared static state.

Visual changes require a parity sweep and inspection of the captured images. A passing comparison
alone cannot show that a feature rendered correctly: both renderers might have drawn nothing or used
the same missing asset. A new feature needs a SceneLab preset and a matching native reference.
Disable the feature once to confirm the comparison detects its absence.

### Visual comparison

With a game loaded, run these commands in the console:

```text
r_managed_scene 1
r_managed_scene_compare 120
```

The comparison waits 120 camera renders, then captures the next camera through both renderers.
Results go to the console and images to `game/screenshots/managed_scene/`. Allow the scene to finish
loading before comparing.

In SceneLab, use **View > Compare With Native** for the current scene. To run a preset sweep, launch
the built-in runner directly from `game/`:

```text
bin/managed/scenelab.exe -parity
bin/managed/scenelab.exe -parity "Sun Shadows;Reflections" -zoom 0.3
```

The sweep writes its results and captures to `game/screenshots/scenelab/parity/`, and exits with code 1
if any comparison fails. Use `-parity-out <folder>` to choose another output location.

- `<scene>.png`: the managed frame.
- `<scene>.native.png`: the native frame.
- `<scene>.diff.png`: the managed frame dimmed, with differences highlighted in red.

Effects with shared or temporal state need isolated comparisons. Use `-parity -isolated` for those checks;
AO and SSR presets already request isolation. Test scene changes as well as initial rendering, and
check representative games when changing the bridge.

Changes to the shared shadow mapper also require a native-to-native comparison. Run
`bin/managed/scenelab.exe -parity -save-native before` from `game/`, make the change, then run
`bin/managed/scenelab.exe -parity -compare-native before`.
For deterministic presets, those frames should be byte-identical.

### Performance and frame inspection

From `game/`, run:

```powershell
bin/managed/scenelab.exe -scene "All Shadows"
bin/managed/scenelab.exe -benchmark "10k Boxes;50k Mixed" -novsync
```

Benchmark results go to `game/screenshots/scenelab/benchmark.md`. Warm up maps before measuring so asset
loading, JIT compilation and shader compilation do not dominate the result. Include scene switching
when testing resource lifetime.

Use `overlay_scene_plan 1` in a game, or **View > Frame Plan** in SceneLab, to see active layers,
resource dependencies, recording segments and timings. Level `2` also shows skipped layers.
Tracy zones are defined in [Diagnostics/Zones.cs](Diagnostics/Zones.cs); they cover collection,
preparation, setup, recording, worker waits and submission. SceneLab's `-serialrecording` switch helps
compare serial and parallel execution.

## Development conventions

Read the relevant section of the [renderer reference](../../docs/managed/scene-renderer.md) before
changing a subsystem. When porting behavior, cite the native function or file in the code comment so
the implementation can be checked against its source.

### Allocation and threading

- Steady-state per-frame work must not allocate. Reuse buffers and pool per-view state by stable
  identity, such as a shadow map and face. `PipelineTests.CollectAndPrepareDoNotAllocate` checks the
  collection and preparation path, including shadows.
- Avoid LINQ, interface-based enumeration, iterators, allocating `params` calls, string formatting,
  capturing lambdas and repeatedly created delegates in hot paths.
- Resolve textures, bindless indices and other native resources during Setup, not Collect or Prepare.
- Worker jobs read prepared state and write only their own context, attributes and view-local lists.
  Warm mutable caches before dispatching jobs; do not lazily create materials on a recording thread.
- Mark work that needs `Graphics` or other main-thread-only engine code through
  `RenderFeature.CanRecordOffMainThread`.
- Each pass binds its own targets, viewport, depth range and view constants through `BeginPass` or
  `BindFrame`. Use `SetPassCombo` for pass-local shader choices. A pass must not depend on state left
  by the previous pass.

### GPU resources and interoperability

Keep native rendering calls in `Gpu/RenderContext`. Add new native helpers through `rendertools` and
the `.def` bindings, then regenerate with `tools/BuildBindings.bat`. Never hand-edit generated interop.
Native changes require a native build; shader changes require shader compilation before parity testing.

Dispose owned GPU resources before native shutdown. Cached materials returned by `Material.FromShader`
are shared; only destroy materials created and owned by the caller. Context state changes must go
through `RenderContext` helpers so cached material bindings are invalidated correctly.

GPU structs must match the native and shader layouts, including packing and matrix conventions.
Check those layouts when updating native dependencies. Skinning uses the engine's shared vertex cache;
morph and vertex-cache preparation needed by this renderer must complete before its draw submission.

### C# style

Use the engine's `Matrix`, `Vector2`, `Vector3`, `Vector4` and `Rotation` types. They already wrap
`System.Numerics`; direct Numerics usage needs a concrete reason, such as an interface that requires it.
Avoid shorthand aliases such as `V3`, `V4` and `M4`. Remember that `Vector3 ==` is approximate; use
`Equals` when cache invalidation or validation requires exact equality.

Follow the repository's `.editorconfig`: tabs, spaces inside parentheses, `_camelCase` instance fields
and `s_camelCase` mutable static fields. Prefer collection expressions and target-typed `new` where
appropriate, while retaining preallocated capacity for scratch arrays. Document public types and
members (`CS1591` is an error here), and give internal types a summary explaining their role.

## Extending the renderer

### Add an object type

1. Derive from `RenderObject` in `World/Objects/`. Override `SizeCulled` for objects that should stay out
   of the size-culling tree, as lights and probes do.
2. Add its preparation and drawing in `Pipeline/Features/`, using the `RenderFeature` contract.
   Include shadow support and the main-thread recording requirement where applicable.
3. Register the feature in `RenderSystem`. Registration order matters; light binning runs first.
4. Add bridge synchronization where needed, a SceneLab preset, and its native equivalent in `NativeScene`.

### Add a layer

Add the layer in `Pipeline/Layers/` and register it in `RenderSystem.Layers.cs` at the appropriate point.
Declare resource `Reads` and `Writes`; let dependency planning decide whether a producer's output is
needed rather than duplicating all reader conditions in `IsNeeded`.

Mesh layers select their run lists and shader mode. Additional run lists belong in `MeshRuns` and the
classifier in `MeshRenderFeature.Lists.cs`, with a corresponding `ClassifierTests` case.

### Add shader inputs or bridge state

Use native view setup and light-binning code as the reference for shader inputs. Missing constants
often produce incorrect pixels without an error. `ViewEnvironment` supplies shared environment inputs;
each pass must still supply constants for its own target and viewport.

Bridge changes must preserve native object flags, layer membership, material overrides and camera
attributes. Read native draw state rather than deriving it again. New state setters must notify the
mirror, including changes made indirectly by native code. Verify the scene both before and after a change.

## Debugging and remaining work

For incorrect frames, start with pass constants, resource dependencies, object flags and shader modes.
Use debug views to isolate shading terms and inspect the depth chain for depth-dependent effects.
Check that the assets exist and that both renderers have comparable temporal history.

The [renderer reference](../../docs/managed/scene-renderer.md) records detailed investigations into
color space, shadow behavior, skinning, material binding, bridge synchronization and parallel recording.

Remaining work includes moving more camera effects into this renderer, threading main-view collection
and preparation, and reducing native per-draw overhead. Known gaps include morphs on meshes without a
skeleton, glass shadow dithering differences, layer-specific attribute overrides and DDGI parity with
baked test data. An intermittent native shutdown crash has also been observed in 256-light scenes;
it still needs a native stack trace.

---

### Overview (`engine/Mounting/Sandbox.Mounting.Source1/README.md`)

# Source 1 mount

The engine's `gmod` mount reads supported models, materials, textures and sounds
from GMA/VPK archives and loose files inside the Garry's Mod installation.

## Registration and archives

The installation is inventoried in one filesystem pass, including nested addon,
gamemode content, download and cache folders. Directory links/reparse points are
skipped to avoid cycles or leaving the installation. Unreadable directories are
skipped with a warning while other directories continue mounting. Files directly
in the installation root are included; top-level `materials`, `models`, `sound` and
`scripts` directories retain their canonical prefixes.

Archives precede loose files. Archives inside `garrysmod` take priority over
other installation folders, with GMAs before VPKs in each priority group and
case-insensitive path sorting for stable first-match resolution. Loose content
keeps its indexed paths without unprefixed addon or gamemode aliases.
Shadowed loose files are logged at trace verbosity; the first indexed source
continues to win.
Model registration uses only companion filenames; models without a VVD/DX90 VTX
pair or a PHY companion remain indexed for dependencies but are not advertised.
Texture headers register the supported individual frames and animation arrays.

GMA versions 1-3 share the VPK reader's leased random-access stream implementation.
Indexing reads directories, not whole payloads; assets load lazily without
extracting archives. Invalid paths, duplicate GMA entries and out-of-bounds data
are rejected. Invalid or unreadable archives are logged as warnings without
marking otherwise valid mounted content as erroneous. Split VPK chunks with a
matching directory archive are skipped, and alternate names for the same VPK
directory are deduplicated.

Only supported model, material, texture and sound resources are registered;
Lua is not executed and BSP/map import is not included. Local Workshop GMA files
inside the installation are supported, but external Steam Workshop folders and
automatic Workshop downloads are not discovered. Remount after downloading or
changing content.

## Material dependency resolution

VMT parameters target Windows PC DX90/PS2.0b. Shader fallback blocks such as
`>=dx90_20b` and the `$DX90_20B` condition are supported. Both combine soldier
skins keep their base texture and self-illumination mask from those blocks.

Loose models resolve their MDL material paths within their enclosing content root
before falling back to ordinary mounted content. VMT patch includes, relative
KeyValues directives, textures and HDR environment maps keep that same root.
For example, `addons/example/models/` resolves against
`addons/example/materials/` without adding global aliases. Full indexed resource
paths keep separate addons' materials and texture caches distinct. VMTs retain
their actual indexed entry paths rather than receiving an extra `materials/`
prefix. Reimport existing models to replace old error-material bindings.

There is no arbitrary basename matching or cross-installation dependency
fallback. Genuinely missing assets remain reported rather than receiving
unrelated replacement materials.

---

### Overview (`engine/Sandbox.Tools/Mcp/README.md`)

# MCP tools and sbox-cli

For agent-facing usage, see the [`sbox-cli` skill](../../Launcher/SboxCli/skills/sbox-cli/SKILL.md),
which ships beside the executable and is printed by `sbox-cli --skill sbox-cli`. Agents find it
through a small bootstrap skill ([`bootstrap/sbox`](../../Launcher/SboxCli/bootstrap/sbox/SKILL.md),
also published as `Facepunch/sbox-skills`), installed for the user. The editor offers it once when
it finds Claude Code or Codex; Editor Preferences › AI Agents and `sbox-cli --skill-install` manage it.

`game/bin/managed/sbox-cli.exe` is a .NET 10 console application that keeps one MCP connection
open over stdin/stdout, or makes a single call with `--call`. It has no engine dependencies of its own. Editors expose a local
named pipe by default, controlled by **Project Settings > AI > Enable MCP Communication**;
games expose one when launched with `-mcp`. The pipe is restricted to the current
OS user and named using the process ID and start time. On Windows the CLI also verifies the
server's actual PID. Multiple editors and games can therefore run independently without TCP ports.

The preference is stored in the `.sbproj` and shared with collaborators. Saving MCP as disabled
immediately closes connected transports and cancels pending work; already running scripts must
cooperate with cancellation.

```powershell
# Discover running instances that support the new transport.
game/bin/managed/sbox-cli.exe --list

# One call against the editor for the project in the current directory, then exit.
game/bin/managed/sbox-cli.exe --call editor_status
game/bin/managed/sbox-cli.exe --call search_tools '{"query":"scene"}'

# Attach; closing the CLI leaves the process running. Takes a PID, a project, or nothing for
# the project containing the current directory (or the only instance, outside any project).
game/bin/managed/sbox-cli.exe --connect
game/bin/managed/sbox-cli.exe --connect 12345

# Attach to the project's editor if it's open, otherwise launch and own one.
game/bin/managed/sbox-cli.exe --project C:/projects/my-game

# Create a project from a template, like the launcher does. Prints the new .sbproj path.
game/bin/managed/sbox-cli.exe --templates
game/bin/managed/sbox-cli.exe --new "My Game" --template game.minimal --in C:/projects

# Launch and own a game.
game/bin/managed/sbox-cli.exe --game C:/projects/my-game/my-game.sbproj
game/bin/managed/sbox-cli.exe --game facepunch.sandbox
```

A project is a `.sbproj` or the directory containing it. Inside a project, the CLI only ever
attaches to that project's instance. Launches open normal visible windows. `--engine <directory>` overrides the game installation
inferred from the CLI's location. Owned launches currently require Windows. An existing process
must be running an engine build with the pipe support; restart older editors after rebuilding.

## Stdio protocol and session files

Configure an MCP client to run the CLI with one connection mode and keep that process open.
Do not start a new CLI for every request. Each request/response is one UTF-8 JSON-RPC line:

```jsonl
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-agent","version":"1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"instance_status","arguments":{}}}
{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"search_tools","arguments":{"query":"scene"}}}
```

Wait for the initialization response before sending subsequent messages. Its `instructions`
explain tool discovery, conventions and the session exchange directory. `_meta` contains `pid`,
`kind`, `project` and `exchangeDirectory` for programs. The unique directory is created under
`%TEMP%/sbox-cli/`; use it for scripts, screenshots and other files shared with the instance.
It is retained after disconnect so outputs remain usable. Copy wanted outputs to the project,
then remove the session directory when finished. Discovery alone does not create directories.

Stdout contains only protocol messages in connection modes. Startup messages and diagnostics
go to stderr. Requests are limited to 8 MiB, responses to 64 MiB, with at most 32 outstanding
requests per CLI. Each connection executes requests in order; separate connections share the
engine main thread. Tools, discovery and binding run on that thread in game scope.

## C# scripts

Use `search_tools` with `csharp` to find `run_csharp`, then call it directly or through `call_tool`:

```jsonl
{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"run_csharp","arguments":{"code":"return Game.ActiveScene?.Name;"}}}
```

Supply a C# async method body in `code`, or an absolute UTF-8 file `path` (not both).
`System`, `System.Linq`, `System.Threading`, `System.Threading.Tasks`, `Sandbox`, and the game
global namespace are imported. `await`, `return`, `exchangeDirectory` and `cancellationToken`
are available. References come from loaded assemblies, including the active addon assemblies;
editor types can be fully qualified in an editor. Compile diagnostics use script line numbers.
This is trusted local automation with full process privileges, and mutations have no automatic
undo. The protocol, transport, registry, session state and built-in tool classes are internal.
Built-in tool methods are also internal and described only in a private MCP registry; never add
`[Expose]`, `[Library]` or `[ActionGraphInclude]` to them. Game code must not reach them through
its TypeLibrary.
Only the tool declaration attributes, hints and `McpResult` builders are public for addon authors.

## Lifetime

Closing stdin, losing the pipe, or Ctrl+C shuts down the bridge. An attached instance
(`--connect`, `--call`, or `--project` on an editor that was already open) is never terminated.
For an instance it launched, the CLI first requests a graceful quit,
waits up to two seconds, then closes its Windows job to terminate the owned process tree.
The instance is assigned to the job atomically during creation, before running; the job also closes if the
CLI crashes or is forcibly killed. Engine processes do not inherit the CLI's stdio handles.

The defaults are `--startup-timeout 180`, `--idle-timeout 600`, and `--request-timeout 300`
(seconds, configurable from 1 to 86400). Idle timeouts apply only without outstanding requests;
logs and other unsolicited output do not renew them. A running request has its own deadline.
On timeout the connection closes. Queued calls are cancelled on disconnect or after a 30-second
main-thread pickup timeout. Running code cancels cooperatively; use `cancellationToken` in
long scripts. A running mutation may have completed before disconnection, so inspect state
before retrying. An attached instance is never killed to stop a stuck script.

## Tool discovery

A tool is a static method. There is no registration - discovery goes through the editor or game TypeLibrary,
so tools in addons and hotloaded assemblies appear and disappear as code compiles. Write the
method, save, and an agent can call it.

The registry and protocol live in `engine/Sandbox.Engine/Systems/Mcp/`.
The established `Editor.Mcp` attribute/result namespace remains available for source and binary
compatibility, including game tools. Editor-specific tools stay in `Sandbox.Tools` and editor
addons.

## Adding a tool

```csharp
namespace Editor.Mcp;

[McpToolset( "car", "Everything about the cars in the scene" )]
public static class CarTools
{
	/// <summary>
	/// Find cars by name. Returns each car's id - pass one to get_car for details.
	/// </summary>
	/// <param name="query">Case insensitive substring of the car's name. Empty matches everything.</param>
	/// <param name="limit">How many results to return. Default 20, max 100.</param>
	[McpTool( "find_cars" )]
	public static object FindCars( string query = "", int limit = 20 )
	{
		...
		return new { Total = total, Cars = cars };
	}
}
```

That's everything. In addon code the XML summary becomes the tool description and `<param>` docs
become the parameter descriptions (codegen turns them into attributes). In engine assemblies there
is no codegen - put `[Description( ... )]` on the method and each parameter instead.

The description is the API. Agents pick tools by reading it, so say what the tool returns, what to
call next, and anything surprising. Cross reference other tools by name - "as returned by
find_cars" tells an agent exactly where a value comes from.

- `[McpTool]` - marks the method. Pass a name (`"find_cars"`) or one gets derived from the method
  name in snake_case (`FindCars` → `find_cars`). Names are public API - agents' workflows break
  when they change.
- `[McpToolset]` on the class groups its tools under a name and description, browsable through
  `list_toolsets`. Without it the name derives from the class name (`CarTools` → `car`).

Tools don't go in the client's static tool list - agents find them live through `search_tools` and
invoke them through `call_tool`, which is what keeps the registry fresh across hotloads. (The
handful of built in entry points are listed via the internal `[McpListed]`, and that's deliberate -
a list a client fetched once must never go stale.)

## Parameters

Parameters become the tool's json schema. A parameter with a default value is optional; without
one it's required.

| C# type | What the agent sends |
|---|---|
| `string` | A string. Anything else gets stringified, so this is the most forgiving type. |
| `bool` | true / false |
| `int`, `long`, `byte`... | An integer |
| `float`, `double`, `decimal` | A number |
| enums | The name as a string, case insensitive. The schema lists every value. |
| `Vector2/3/4`, `Vector2Int/3Int`, `Angles`, `Rotation` | A comma string - `"100,0,50"`, `"pitch,yaw,roll"` |
| `Guid`, `DateTime`, `TimeSpan`, `char` | A string |
| `T[]`, `List<T>` | An array of T |
| `Dictionary<string, T>` | An object with T values |
| `JsonObject`, `JsonArray`, `JsonNode` | Raw json, taken as sent |
| A class or struct you wrote | An object - see below |

Binding is deliberately tolerant, because agents make predictable mistakes: argument names match
case insensitively, values that arrive json-encoded inside a string get unwrapped, numbers arrive
as strings, enum names in the wrong case still count. Unknown argument names fail loudly - silently
ignoring a typo'd argument does the wrong thing invisibly.

## Custom classes and structs

A plain data type works as a parameter (or inside one - arrays, lists, nested properties). The
schema is generated from its public settable properties:

```csharp
/// <summary>
/// One waypoint on a patrol route.
/// </summary>
public class Waypoint
{
	/// <summary>Where to stand, as 'x,y,z'.</summary>
	public required Vector3 Position { get; set; }

	/// <summary>Seconds to wait before moving on.</summary>
	public float Wait { get; set; }
}

[McpTool( "set_patrol" )]
public static object SetPatrol( string id, Waypoint[] waypoints ) { ... }
```

- Every public property with a public setter becomes a schema property, advertised in camelCase
  (`Position` → `position`). Binding is case insensitive so both spellings work.
- The C# `required` keyword makes the property required in the schema, and the serializer enforces
  it - a call missing `position` fails with an error saying so.
- Describe properties the same way as everything else - XML summaries in addon code,
  `[Description]` in engine code.
- `[JsonPropertyName]` and `[JsonIgnore]` are respected.

Engine and framework types don't get their properties reflected - most serialize through custom
converters (`Vector3` is the string `"x,y,z"`, not `{x, y, z}`), so a guessed shape would be wrong.
They keep an open schema; prefer the types in the table above.

## Return values

Return whatever is most honest, it gets shaped into an MCP result:

| Return | What the agent gets |
|---|---|
| `string` | The text as is |
| Any object | Serialized to json - anonymous objects are the usual shape |
| `GameObject` / `Component`, at any depth | A compact reference - `{ "type": "PointLight", "id": ..., "gameObject": ... }` - never a serialized scene subtree. The id chains into get_game_object. |
| `Resource` (Model, Material, any GameResource) | Its resource path - `"models/chair.vmdl"` - which chains into asset_info and friends. That's the engine's own serialization; no special handling. |
| `Bitmap` | A png image |
| `McpResult` | Full control - compose text and image blocks, e.g. `McpResult.Image( thumb ).WithText( path )` |
| `null` / `void` | "ok" |
| `Task<T>` / `async` | Awaited, then shaped as above |

Shape returns for a reader, not a serializer: return counts alongside truncated lists
(`new { Total, Showing, Assets }`), format dates as strings, and leave out fields nobody asked for.

Declare a data class as the return type instead of `object` and the tool advertises an
`outputSchema` generated from its properties, and every result carries `structuredContent` the
client can bind to it. Anonymous returns stay plain text - use a class when the shape is stable
and worth promising.

## Errors

Throw. The exception's message reaches the agent as an error result it will read and adapt to, so
write the message for that reader: say what was wrong and what to do instead.

```csharp
throw new Exception( $"No car named '{name}'. Find cars with find_cars." );
```

Everything from tool lookup onward goes back in-band - unknown tools, arguments that don't bind,
failures thrown mid-tool - written with the same care: suggestions for near-miss names, the tool's
real parameter list. The agent reads the error and self corrects. You get all of that for free.

## Threading and lifetime

Tools run on the editor main thread - touch scenes, assets and widgets freely, no marshalling. If
the main thread doesn't pick the call up within 30 seconds (modal dialog, long operation) the agent
is told the editor is blocked; the queued call is cancelled and will not run later.

Tools can be `async`. The main thread invokes the method, the result is awaited off it - so do slow
work (network, compiles) after your last touch of engine state, or hop back with `MainThread`.

## Conventions

Registry-wide rules live in one place - the `Instructions` string in `McpProtocol.cs`, which every
client injects into its agent's context at connect. Add new rules there instead of repeating them
per tool. The current ones:

- Paging is `limit` and `offset`. Say the default and max in the parameter description.
- Vectors and angles are comma strings - `'x,y,z'`, `'pitch,yaw,roll'`.
- Coordinates are Source engine convention: one unit is one inch, +x forward, +y left, +z up,
  angles in degrees.
- Game objects and components are identified by guid, assets by the relative path `asset_search`
  returns.

## Trying it out

From the project directory, `sbox-cli --call search_tools '{"query":"car"}'` finds your tool and
`sbox-cli --call find_cars` calls it. For an MCP client, configure a stdio server with the full
path to `sbox-cli.exe` and `["--connect"]` (or `["--project"]`) as arguments; the client's working
directory selects the project.

Then ask it to `search_tools` for yours and call it. `read_console` shows what the editor logged,
which is also where your tool's errors and prints end up.

---

### Overview (`engine/Tools/InteropGen/docs/README.md`)

# InteropGen

InteropGen generates the C++↔C# bindings for the engine. It reads `.def` files (a small DSL
describing the classes and structs that cross the native/managed boundary) and writes, for each def:

- a managed file (`cs` directive) - typed C# wrappers, `[UnmanagedCallersOnly]` exports and the
  `NativeInterop` bootstrap
- a native header (`hpp` directive) - declarations of the managed classes native can call
- a native source file (`cpp` directive) - exported thunks, import function pointers and the
  `igen_*` initializer

The generated output is **not committed** (see `.gitignore` - `Interop.*.cs` / `interop.*`); the
build regenerates it, and files whose content didn't change are left untouched.

## Documentation

| File | Contents |
|------|----------|
| [def-files.md](def-files.md) | The `.def` file format: top-level directives, includes, inherit/skipall |
| [classes.md](classes.md) | Declaring classes, structs, enums; functions, variables, attributes |
| [types.md](types.md) | Argument types, marshalling, flags (`out`, `ref`, `CastTo[...]`, ...) |

## How it runs

The build (`Tools/SboxBuild`) calls `Facepunch.InteropGen.Program.ProcessManifest( "engine" )` from
the repo root. That reads `engine/manifest.def` - a plain list of `.def` files - and processes each
listed def in parallel. Files whose content didn't change are not rewritten.

## Project layout

```
Program.cs        Entry point - reads the manifest, processes each def
Logger.cs         Per-thread buffered console output so parallel defs don't interleave
Parsers/          Line-oriented parsers for the .def DSL (global → class → inline body)
Definition/       The model: Definition, Class, Function, Variable, Struct
Arguments/        One Arg type per marshallable type - each knows its C#/C++ types and conversions
Pipeline/         InteropPipeline (parse → resolve → sort → mangle → hash), TypeResolver, Mangler
Writer/           The three emitters (ManagedWriter, NativeWriter, NativeHeaderWriter) and helpers
```

The pipeline (`Pipeline/InteropPipeline.cs`) turns a def file into a resolved `Definition`:

1. **Parse** - `GlobalParser` reads the file (and its includes) line by line
2. **Resolve** - `TypeResolver` links base classes, pulls inherited functions down, and converts
   every unknown type name into a concrete `Arg`
3. **Sort** - structs and classes are ordered by name so output is deterministic
4. **Mangle** - `Mangler` gives every function/variable a unique C-safe export name
5. **Hash** - the def text is hashed; both sides verify it at runtime so a stale binary fails fast

## Invariants - read before changing anything

The native and managed sides are built from the same generated files, and the runtime verifies the
def hash when the tables are exchanged - so a stale native/managed pairing fails fast. When
changing the generator:

- **Never** change the hash computation, the name mangling or the sort order unless both sides will
  be regenerated and rebuilt together (they normally are - the build runs the generator).
- A pure refactor should be verified by snapshot-diffing the output, since the generated files
  aren't in git: run the generator before your change, hash every output, run it again after, and
  compare.

```
# from the repo root, with a tiny console app calling Program.ProcessManifest("engine"):
dotnet run                          # before the change
<hash engine/Sandbox.*/Interop.*.cs and src/**/interop.*>   # snapshot A
dotnet run                          # after the change
<hash again>                        # snapshot B - must equal A for a pure refactor
```

Two ordering details that are easy to break:

- The native export table (casts, functions, variable get/set) is index-matched between C++ and C#.
  Both emitters drive it from `Writer/NativeExportTable.cs` - the single source of truth for slot
  order. Don't enumerate slots anywhere else.
- Mangled names de-duplicate collisions with numeric suffixes in encounter order, so reordering
  classes or members in a def can rename exports. That's fine - both sides regenerate together -
  but it's why output must always be regenerated as a pair.

---

### Overview (`game/unittest/addons/dmmod/readme.md`)

This addon references online package "facepunch.dm98"

To prove:

1. Local uncompiled projects can reference remote packages
2. Local projects can compile using remote packages
3. Local projects can use remote package's base.dll instead of the local

---

### Overview (`engine/Tests/Sandbox.Test.Unit/Layout/Tools/README.md`)

# Layout conformance fixture tools

Both fixture sets come from upstream repositories and are regenerated with these scripts (Python 3):

- `convert_flex_fixtures.py <upstream>/tests/generated ../FlexConformance` - converts Yoga's Chrome-generated gtest fixtures
  ([facebook/yoga](https://github.com/facebook/yoga/tree/v3.2.1), tag **v3.2.1**, MIT) into the `[TestClass]` files in `../FlexConformance/`.
- `pack_layout_fixtures.py <upstream>/tests/xml ../../Data/Layout block grid blockgrid gridflex` - packs Taffy's
  Chrome-generated XML fixtures (DioxusLabs/taffy revision
  [`ac2b86929d35b7e0f1d24919595b89b4ce89baa4`](https://github.com/DioxusLabs/taffy/tree/ac2b86929d35b7e0f1d24919595b89b4ce89baa4), MIT) into one `layout-<group>.xml` per group for
  `BlockGridFixtureTests`. The checked-in files match that revision byte-for-byte apart from their wrapper.
- `update_layout_signatures.py <results.trx> ../../Data/Layout/layout-known-failures.txt` - prints an updated
  known-failure baseline after a deliberate layout change. Run the block/grid conformance tests with a TRX logger, review every
  changed mismatch, and use the output to update the baseline; never refresh signatures blindly.

Local suite and file names are neutral; generated source comments and XML headers retain upstream attribution.
Flex fixture factories explicitly assign omitted source styles; production nodes always use CSS defaults.
`FixtureGeometry` compares raw results in the source fixture's rounded coordinate convention without mutating
nodes. This is test observation only, not an alternate production layout mode. Block/grid fixtures retain
rectangle and child-count assertions; resolved-track diagnostics are no longer collected in production.
Full MIT notices are comments in `Sandbox.Layout/Layout/Flex/FlexLayout.cs` and `Sandbox.Layout/Layout/Grid/TrackSizing.cs`;
the layout assembly embeds these sources so binary distributions also retain the notices.
Signatures hash only the comparison lines (LF-normalized, with a trailing blank line), not diagnostic paths,
suite names, or input/actual tree dumps. Renaming the suites and data files does not require refreshing them.

---

### Engine Tools InteropGen Docs Classes (`engine/Tools/InteropGen/docs/classes.md`)

# Classes, structs and members

## Declaring classes

```
native class IPhysicsBody as NativeEngine.IPhysicsBody
{
	void SetMass( float f );
	float GetMass();
}
```

Every class declaration starts with `native` or `managed` - which side *implements* it:

- **`native class`** - implemented in C++, imported into managed. The generator emits a C# wrapper
  struct holding the native pointer, with a method per function.
- **`managed class`** - implemented in C#, exported to native. The generator emits a C++ class
  (in the native header) whose methods call into managed, and `[UnmanagedCallersOnly]` thunks on
  the managed side. Instances are addressed by an object id resolved via `Sandbox.InteropSystem`.

The forms:

| Form | Meaning |
|------|---------|
| `native class X` | Instance class - wrapper carries a `self` pointer. |
| `native static class X` | No instances; members are called as `X::Member()`. A static class whose native name starts with `global` binds free functions at global scope (`::Member()`). |
| `native accessor g_pThing` | Like a static class, but members are reached through a global pointer: `g_pThing->Member()`. The generated thunk asserts the pointer isn't null. |
| `managed class X` / `managed static class X` | Same split, implemented in C#. |

### Names and aliasing

`as` gives the type a different name on the other side:

```
native class CVideoPlayer as NativeEngine.CVideoPlayer
managed static class Sandbox.Engine.Bootstrap
```

The first name is the implementing side's name, the `as` name is the other side's. Namespaces use
`.` (or `::` for native) and are split off automatically. Without `as`, both sides use the same
name.

### Base classes

```
native class CSceneObject as Sandbox.SceneObject
native class CSceneModel as Sandbox.SceneModel : Sandbox.SceneObject
```

`: Base` references another class *in the same def*. The derived wrapper inherits all of the base's
non-static functions and gets implicit/explicit conversion operators to and from every base. The
conversions go through native `dynamic_cast`, because with multiple inheritance base pointers can
differ from derived pointers.

## Functions

```
bool Play( string pUrl, string pExt );
static CVideoPlayer Create( Sandbox.VideoPlayer managedObject ); [new]
void Destroy(); [delete]
int GetMemRequired( int width, int height ) const;
```

- `static` members don't take a `self`/instance argument.
- A trailing `const` is accepted (and required to match a const native method on managed classes).
- Overloads are fine - mangled export names are de-duplicated automatically.
- A function named `GetType` is emitted as `GetType_Native` in C# (to avoid `object.GetType`).

**Specials** after the semicolon:

| Special | Meaning |
|---------|---------|
| `[new]` | The thunk runs `return new NativeType( args );` instead of calling a method. |
| `[delete]` | The thunk runs `delete (NativeType*)self;`. On the managed side the wrapper also nulls its own pointer afterwards. |

### Inline functions

A function starting with `inline` carries its native body in the def file, instead of binding to an
existing native method:

```
inline void SetBoneName( string boneName )
{
	self->m_boneName = boneName;
	self->m_pBoneName = self->m_boneName.String();
}
```

The body is emitted into the generated .cpp. Parameters arrive already converted to their native
types under their declared names (`self` is the typed instance pointer; `this->` is rewritten to
`self->`). `return` expressions are marshalled like a normal return value.

## Variables

A field declaration generates a get/set thunk pair and a C# property:

```
native class SheetSequence_t
{
	uint m_nId;
	bool m_bClamp;
	float m_flTotalTime;
}
```

## Structs, enums and pointer handles

Value types that cross the boundary are declared at file level:

```
native struct MeshTraceInput
native enum ButtonCode_t is NativeEngine.ButtonCode;
native pointer SwapChainHandle_t;
```

- **`struct`** - both sides must define the type with an identical layout. Sizes are verified at
  startup (`structSizes` exchange); a mismatch is a fatal error. Structs 8 bytes or larger are
  passed by pointer when used as parameters; smaller structs must be marked `[small]` and are
  passed by value.
- **`enum`** - passed as a 64-bit integer and cast on both sides.
- **`pointer`** - a native type that pretends not to be a pointer (`DECLARE_POINTER_HANDLE`).
  Managed treats it as an opaque `IntPtr`-sized struct.

`as` / `is` (synonyms) alias the managed name, same as for classes.

## Attributes

Attributes are written on the line(s) before a declaration and apply to the next class, struct or
function parsed.

### On functions

| Attribute | Meaning |
|-----------|---------|
| `[nogc]` | Emits `[SuppressGCTransition]` - the call skips the GC transition, so it must be short, non-blocking and never call back into managed. On a class, applies to all of its functions. |
| `[callback]` | Marks a function that can re-enter managed. Cancels `[nogc]` inherited from the class. |

### On classes

| Attribute | Meaning |
|-----------|---------|
| `[Handle:Managed.Type]` | Instances cross the boundary as handle ids instead of raw pointers. Managed resolves them with `Sandbox.HandleIndex.Get<T>`, native with `GetManagedHandle`. |
| `[ResourceHandle:HModel]` | The class is accessed through a strong resource handle (`HModelStrong`). Thunks dereference the handle (returning default if unloaded), and the class automatically gains handle-management functions: `DestroyStrongHandle`, `IsStrongHandleValid`, `IsError`, `IsStrongHandleLoaded`, `CopyStrongHandle`, `GetBindingPtr`. |
| `[SharedDataPointer]` | For Qt-style classes that are just a smart pointer to shared data. The wrapper is a public class (not a struct) that copies by value across the boundary and queues a `Dispose` from its finalizer. |
| `[WindowsOnly]` | When generating on a non-Windows platform, the native thunks are emitted as stubs returning default values. |

### On structs

| Attribute | Meaning |
|-----------|---------|
| `[small]` | The struct is smaller than a pointer (8 bytes) and is passed by value. Required - the generated code `static_assert`s that unmarked structs are at least 8 bytes. |

---

### Engine Tools InteropGen Docs Def Files (`engine/Tools/InteropGen/docs/def-files.md`)

# .def file format

A `.def` file is parsed line by line. Blank lines are skipped, and lines starting with `//` are
comments. Every other line is one of:

- a **directive** - the first word is the keyword, the rest of the line is its argument
- a **type declaration** - `native class ...`, `managed struct ...`, etc. (see [classes.md](classes.md))
- an **attribute** - `[nogc]`, `[Handle:...]`, etc., applying to the next declaration

Directive arguments may be wrapped in double quotes; the quotes are stripped. The keyword doesn't
have to start the line - `#include "foo.h"` works because the parser matches the first word-run it
finds, which is handy for keeping C++ syntax highlighting happy in def files.

## Top-level directives

| Directive | Example | Meaning |
|-----------|---------|---------|
| `ident` | `ident "engine"` | Name of this binding set. Becomes the native init export `igen_engine` and namespaces handle types. |
| `nativedll` | `nativedll engine2.dll` | The native dll managed loads at startup (extension is stripped; resolved per-platform). |
| `cs` | `cs "../Sandbox.Engine/Interop.Engine.cs"` | Where to write the managed output, relative to the def file. |
| `cpp` | `cpp "../../src/engine2/interop.engine.cpp"` | Where to write the native source. |
| `hpp` | `hpp "../../src/engine2/interop.engine.h"` | Where to write the native header. Per-class sub-headers (`interop.engine.<class>.h`) are written next to it. |
| `namespace` | `namespace "Managed.SandboxEngine"` | The namespace for the generated `NativeInterop`/`Exports` classes - C# namespace on the managed side, C++ namespace on the native side. |
| `exceptions` | `exceptions "Sandbox.Interop.BindingException"` | Managed method called when an exported managed function throws: `(string className, string functionName, Exception e)`. |
| `pch` | `pch "cbase.h"` | Precompiled header `#include`d at the top of the generated .cpp. |
| `include` | `include "engine/*"` | Includes a header, def file or folder - see below. |
| `inherit` | `inherit "engine.def"` | This binding set builds on another - see below. |
| `skipall` | `skipall "tools.def"` | Skip everything another def already covers - see below. |
| `delegate` | `delegate DebugDrawDelegate_t;` | Declares a native delegate type name so it can be used as a parameter type (passed as `IntPtr`, converted with `FunctionPointerToDelegate<T>` on the native side). |

## include

`include` does three different things depending on its argument:

- ends in `.h` → emitted as `#include "..."` in the generated native header. The conventional
  spelling is `#include "vphysics2/iphysicsbody.h"`, which parses identically.
- ends in `.def` → that file is parsed inline, as if its contents were pasted here. Paths are
  relative to the including file.
- otherwise → treated as a folder: every `*.def` in it is included in alphabetical order.
  A trailing `/*` (e.g. `include "common/*"`) recurses into subfolders.

Included def text counts toward the definition hash, so a change in any included file correctly
invalidates the binary pairing.

## inherit and skipall

Several defs can cover overlapping native code. `tools.def` builds on `engine.def`:

```
ident "tools"
inherit "engine.def"
include "common/*"      // same folders engine.def includes
```

`inherit` loads the other def and remembers what it declared. Types declared there are still
*parsed* here (the includes overlap on purpose) so they can be referenced as parameter and return
types - but they are **not re-emitted**: their wrappers already exist in the inherited def's
assembly, and they're excluded from this def's export tables and struct size checks.

`skipall` is the stronger form used when a def chain gets deep (e.g. `tools.hammer.def` inherits
`engine.def`, `tools.def` and `tools.assetsystem.def`):

```
inherit "tools.def"
skipall "tools.def"
```

Everything that appears in the named def - classes (native *and* managed), structs, and its `.h`
includes - is skipped during emission here.

## manifest.def

`engine/manifest.def` is just the list of defs to build, one per line:

```
Definitions/tools.def
Definitions/engine.def
...
```

Lines not ending in `.def` are ignored.

---

### Engine Tools InteropGen Docs Types (`engine/Tools/InteropGen/docs/types.md`)

# Argument types and flags

Every parameter, return value and variable in a def has a type. A type is either one of the
built-ins below, or a name resolved against the def's declared classes, structs, enums, pointers
and delegates.

## Built-in types

Built-ins are `Arg` subclasses in `Arguments/`, registered by `[TypeName]` attribute (matching is
case-insensitive).

| .def type | C# | C++ | Notes |
|-----------|----|-----|-------|
| `void` | `void` | `void` | Return type only. |
| `bool` | `bool` | `bool` | Crosses the boundary as `int` (0/1). |
| `byte` | `byte` | `unsigned char` | |
| `ushort` | `ushort` | `unsigned short` | |
| `int` | `int` | `int` | |
| `uint` | `uint` | `unsigned int` | |
| `long` | `long` | `int64` | |
| `ulong` | `ulong` | `uint64` | |
| `float` | `float` | `float` | |
| `double` | `double` | `double` | |
| `intptr`, `void*` | `IntPtr` | `void*` | Raw pointer. |
| `string` | `string` | `const char*` | UTF-8. Pinned for the call going in; copied to a thread-local on the way out (`SafeReturnString`). |
| `CUtlString` | `string` | `CUtlString` | Like `string` but the native side holds a `CUtlString`. |
| `stringtoken` | `Sandbox.StringToken` | `uint32` | Built native-side with `StringTokenFromHashCode`. |

Qt types, used by the tools defs:

| .def type | C# | C++ |
|-----------|----|-----|
| `qstring` | `string` | `QString` (crosses as `const QChar*`) |
| `qdir` | `string` | `QDir` |
| `qbytearray` | `string` | `QByteArray` (crosses base64-encoded) |
| `qicon` | `string` | resolved with `FindOrCreateQIcon` |
| `qreal` | `float` | `qreal` |
| `qpoint`, `qpointf` | `Vector3` | `QPoint` / `QPointF` |
| `qsize`, `qsizef` | `Vector3` | `QSize` / `QSizeF` |
| `qcolor` | `Color32` | `QColor` |
| `qrect`, `qrectf` | `QRectF` | `QRectF` |

## Resolving everything else

Any other type name is looked up, in order:

1. a declared **class** by managed name with namespace, then by short managed name
2. a declared **struct/enum/pointer** by managed name (with or without namespace), then native name
3. a declared **delegate**

An unknown type fails the build with `Unknown Type <name>`.

How a resolved type crosses the boundary depends on what it is:

- **native class** - the raw native pointer (`IntPtr` in C#). `[Handle:...]` classes cross as a
  handle id, `[ResourceHandle:...]` classes as a strong-handle pointer.
- **managed class** - a uint object id, resolved through `Sandbox.InteropSystem`.
- **struct** - by pointer if 8+ bytes, by value if marked `[small]`.
- **enum** - as a 64-bit integer, cast on both sides.
- **pointer** - as an opaque `IntPtr`.
- **delegate** - as an `IntPtr`, converted native-side with `FunctionPointerToDelegate<T>`.

## Parameter flags

Flags are written before the type:

```
void GetEngineSwapChainSize( out int w, out int h );
void SetTonemapParameters( ref SceneTonemapParameters_t pParams );
bool ConvertImageFormat( CastTo[uint8*] void* src, ... );
```

| Flag | Sides | Meaning |
|------|-------|---------|
| `out` | C# `out` param | Passed to native as a pointer to write through. `out string` works too - the native side writes a pointer, managed converts it after the call. |
| `ref` | C# `ref` param | Passed as a pointer, readable and writable. |
| `asref` | return values | The wrapper returns `ref T` - a managed ref into native memory (`Unsafe.AsRef`). |
| `cref` | native side | The native method wants a reference (`T&`): the thunk dereferences the incoming pointer, and outgoing calls pass `&value`. |
| `cast` | native side | Cast the value to the parameter's native type when passing it. |
| `CastTo[X]` | native side | Cast the incoming value to the exact native type `X` before use - for types managed doesn't model (e.g. `CastTo[CKV3MemberName] string key`). |
| `stable` | `string` returns | Promises the returned `const char*` outlives the call (an interned name, a member that isn't going away), so the thunk skips the defensive copy into a thread-local that string returns normally get. Only valid on `string` - don't use it for pointers into temporaries. |

## Arrays and literals

- `type[] name` - passes a raw pointer to the first element (`type*` on both sides). The caller
  guarantees the length by convention (usually a separate count parameter).
- `[expression]` as a parameter is a literal: it isn't part of the managed signature at all, and the
  native call site passes the expression verbatim.

---

--- METRICS ---
- Files Extracted: 11
- Estimated Token Budget: ~16019 tokens
- Canonical Reference: https://codewiki.google/github.com/Facepunch/sbox-public
