# Schema fields

Every field on a CS2 entity is generated from the game's own schema, dumped from the live `SchemaSystem` rather than hand-written. Offsets resolve at runtime by name, so a game update moves them without touching plugin code.

`Player` and `Pawn` arrive with accessors already applied:

```ts
import { Player } from '@s2script/cs2';

for (const p of Player.allConnected()) {
	const pawn = p.pawn;
	if (!pawn) continue;
	pawn.health = 100;
	const where = pawn.origin; // Vector | null
}
```

Reads return `null` once a ref goes stale — never garbage. Writes call `notifyStateChanged` for you.

## Entities you create

`createEntity` returns a bare `EntityRef` with no schema fields on it. `wrapEntity` applies a class's accessors to any ref:

```ts
import { wrapEntity } from '@s2script/cs2';
import { createEntity } from '@s2script/sdk/entity';

const prop = createEntity('prop_dynamic');
prop?.spawn();

const fields = wrapEntity('CBaseModelEntity', prop!);
fields.renderMode = 1; // kRenderNone
```

The class name is checked against the generated set, and the return type follows from it — a typo is a compile error rather than an object with silently missing fields.

The wrapper is a **view, not a copy**: it holds the ref and reads through it on every access, so it stays correct as the entity changes and costs nothing to keep on a long-lived object.

367 entity classes are generated — everything deriving from `CEntityInstance` that has fields, plus `CCSGameRules`. Animgraph, mesh, particle and scene internals are excluded; nothing holds an `EntityRef` to those.

## Embedded structs

A field whose type is a struct embedded in the entity becomes a nested accessor:

```ts
const glow = wrapEntity('CBaseModelEntity', ref).glow;
glow.glowing = true;
glow.glowType = 3;
glow.glowRange = 5000;
```

Nesting works at any depth, because an embedded struct is just another `(ref, base offset)` pair:

```ts
const e = wrapEntity('CRagdollProp', ref);
e.collision.collisionGroup = 6; // COLLISION_GROUP_DEBRIS
e.collision.collisionAttribute.collisionGroup = 6; // struct inside a struct
e.collision.solidType = 6; // SOLID_VPHYSICS
```

An embedded struct is only reachable **through its owner** — it has no entity of its own, only an offset inside one, so `wrapEntity` does not accept its name.

If either the owner's offset or the field's offset fails to resolve, the read returns `null` and the write is dropped. It never adds a failed lookup to a valid base and reads whatever happens to be next in memory.

## Value wrappers are flattened

Some schema types exist only to name a scalar — a struct whose single field is `m_Value`. `GameTime_t` and `GameTick_t` are these, and they are the majority of embedded fields on a pawn. They read as plain numbers:

```ts
pawn.deathTime; // number | null   — not { value: number }
pawn.createTime;
```

A struct with one _named_ field keeps its object form, because the name carries meaning that flattening would throw away.

## Enums

Enums are integers of the width their binding declares, read **unsigned** — the storage is unsigned and schema enums are non-negative, so a signed read would turn high-bit flag values negative.

```ts
const e = wrapEntity('CBaseEntity', ref);
e.moveType = 5; // MOVETYPE_FLY
```

They are typed `number`, not named constants — the enumerator names are not yet dumped, so keep your own constants and comment the value.

An 8-byte enum is read-only, for the same reason `uint64` is: there is no narrowing 64-bit writer.

## Colours are packed integers

`Color` fields are a little-endian `uint32` with **red in the low byte**:

```ts
function rgba(r: number, g: number, b: number, a: number): number {
	return ((r & 0xff) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24)) >>> 0;
}

wrapEntity('CBaseModelEntity', ref).render = rgba(255, 0, 0, 255);
wrapEntity('CBaseModelEntity', ref).glow.glowColorOverride = rgba(255, 0, 0, 255);
```

That covers `m_clrRender` (exposed as `render`) and `m_glowColorOverride`.

## What is not exposed

Around 700 fields in the generated set are skipped, and the reason is recorded per field rather than guessed around:

- **raw pointers** and function pointers — not meaningful to hand to a plugin
- **`CUtlVector` / `CUtlString` / `CUtlSymbolLarge`** containers — these need a reader that follows the pointer
- **enums whose binding did not state a width** — a width is never assumed
- **`CNetworkVelocityVector` / `CNetworkViewOffsetVector`** — their components are `CNetworkedQuantizedFloat`, which the schema does not describe, so the layout would have to be guessed

The rule throughout: a field is exposed when the engine tells us how to read it, and skipped when it doesn't. Nothing is derived from a constant borrowed from another framework.

A writable field can still be a **no-op**. `m_flGravityScale` is ignored by the engine (the real setter maintains a second field); `m_vecAbsVelocity` skips the physics update; `m_bodyGroupChoices` is a `CUtlOrderedMap`. Use the engine-call methods on `EntityRef` / `Pawn` — see [Entities](https://s2script.com/docs/concepts/entities).

For creating, spawning and removing entities, entity I/O and ray casts, see [Entities](https://s2script.com/docs/concepts/entities) and the [entity module](https://s2script.com/docs/api/modules/entity).
