CS2OpenDev.Sdk.Entities 1.1.6

This package has a SemVer 2.0.0 package version: 1.1.6+cs.25218825.dump.2026-09-09.
dotnet add package CS2OpenDev.Sdk.Entities --version 1.1.6
                    
NuGet\Install-Package CS2OpenDev.Sdk.Entities -Version 1.1.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CS2OpenDev.Sdk.Entities" Version="1.1.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CS2OpenDev.Sdk.Entities" Version="1.1.6" />
                    
Directory.Packages.props
<PackageReference Include="CS2OpenDev.Sdk.Entities" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CS2OpenDev.Sdk.Entities --version 1.1.6
                    
#r "nuget: CS2OpenDev.Sdk.Entities, 1.1.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CS2OpenDev.Sdk.Entities@1.1.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CS2OpenDev.Sdk.Entities&version=1.1.6
                    
Install as a Cake Addin
#tool nuget:?package=CS2OpenDev.Sdk.Entities&version=1.1.6
                    
Install as a Cake Tool

CS2OpenDev.Sdk.Entities

Typed entity wrappers for Counter-Strike 2, generated from the curated Schema Lens state.

They read through CS2OpenDev.Sdk.Entities.Abstractions and touch nothing else: no schema type, no protobuf message, no parser. That is what lets the same wrappers run over any demo parser implementing the contract instead of one particular runtime.

Published to NuGet.org, and mirrored to GitHub Packages (https://nuget.pkg.github.com/CS2OpenDev/index.json); the .nupkg is also attached to each release.

What you get

61 wrapper classes over the curated set, mirroring the schema's curated hierarchy, and a registry:

// Bind once per class at startup — your runtime builds its own
// ordinal-to-storage map from the manifests.
foreach (EntityClassBinding binding in EntityWrapperRegistry.Bindings)
{
    myRuntime.Bind(binding);
}

// Construct when an entity of a known class appears.
EntityWrapper? w = EntityWrapperRegistry.Create(engineClassName, reader, world);

if (w is CSPlayerPawn pawn)
{
    int health = pawn.Health;                   // absent reads as 0
    int? life  = pawn.LifeState;                // absent reads as null — 0 means LIFE_ALIVE
    Vector3? at = pawn.Origin;                  // absent reads as null — null is the normal case
    ulong buttons = pawn.Buttons;

    uint raw = pawn.ActiveWeaponHandle;         // packed, undecoded
    BasePlayerWeapon? gun = pawn.ActiveWeapon;  // resolved by your runtime to the concrete
                                                // weapon wrapper — every one IS a BasePlayerWeapon
}

Hierarchy and ordinal layout

Wrapper classes derive from each other exactly as the schema's curated classes do: AK47 is a CSWeaponBaseGun is a BasePlayerWeapon, CSPlayerPawn is a CSPlayerPawnBase. Uncurated intermediates (CCSWeaponBase, CEconEntity, ...) are skipped; the chain hops to the nearest curated ancestor. Most concrete weapon classes curate no fields of their own. Their whole read surface is inherited, and their binding is their base chain's layout verbatim.

That "verbatim" carries the correctness of the whole scheme. Each binding's CanonicalPaths is laid out as

layout(C) = layout(nearestCuratedAncestor(C)) ++ ordinal-sort(ownFields(C))

The base chain's ordinal space is the prefix and own fields follow, the way a C++ base subobject sits at offset 0 of every derived object. A base property's ordinal constant is therefore valid through every descendant's binding, which is what lets BasePlayerWeapon.Clip1 read correctly on a wrapper constructed over CAK47's manifest.

For a consumer this sharpens an old rule without changing it: never hard-code an ordinal. Ordinals were always unstable across releases; under prefix layout a curation change to a base class renumbers the own segment of every descendant's binding at once. Bind against the CanonicalPaths array, the only supported way, and renumbering cannot affect you, because wrapper and manifest ship from one emitter pass and always agree.

The layout does ask one thing of the wire: a concrete class's serializer must carry its ancestors' fields. It does. DemoViewer.NET measured it on live entities before this shipped (SDK#30: every gun-chain class carries all composed paths; the shotguns carry the weapon base's fields and none of the gun's, because shotguns are not guns). But that is a fact about the wire, not something this package can enforce. The manifests follow the schema's real parent chain and nothing else, because the wire does too.

Two read policies

Most properties are 0-default: a field that was never received reads as zero, which is harmless when zero is not a meaningful value.

A curated few are seen-aware and typed T?, because a zero would be read as data. Which policy a field gets is a per-field judgement recorded in the generator, not something you can infer from its type, and the reason differs per field, so read the property's <remarks> rather than assuming.

Three reasons so far.

A received zero can be a state. m_lifeState's 0 is LIFE_ALIVE, so a 0-default getter would make a pawn that never transmitted the field indistinguishable from a live one.

The value can never arrive at all. Origin's canonical path names a struct (CNetworkOriginCellCoordQuantizedVector) whose leaves are what the wire carries, so the parent path does not materialise over a GOTV demo, and a 0-default presented that absence as the world origin. Here null is the normal case rather than an edge case: it does not mean the entity is at (0,0,0), and it does not mean your runtime dropped something. A runtime that reconstructs world coordinates from the cell leaves and stores the result under this path serves it through this property.

And a fabricated zero can be a coordinate. The quantized-origin leaves (OriginCellX/Y/Z, OriginVecX/Y/Z on the same three classes) do arrive on the wire, but cell 0 is a legal world cell. The consumer-side reconstruction is (cell − 32) × 512 + offset, so a 0-default would place a never-received entity at −16384 on that axis with full confidence. null means the leaf has not been received yet; on live entities presence is the normal case. The reconstruction arithmetic itself stays on your side of the seam, deliberately.

Handles

A handle property gives you the raw packed uint. The companion property (ActiveWeapon beside ActiveWeaponHandle) asks your runtime to resolve it, and is null when the handle names no live entity of that type.

Only handles whose target is itself a curated class get a companion. m_hOwnerEntity points at CBaseEntity, which this package does not wrap, so it exposes the raw handle alone rather than inventing a type for it.

The weapon companions, ActiveWeapon and LastWeapon, are typed BasePlayerWeapon?. Their handles point at concrete weapons on real demos, and every concrete weapon wrapper now derives from BasePlayerWeapon, so your runtime's dispatch to the concrete class satisfies the typed fold. This is the type they briefly had and lost: under the old flat emission a resolved SmokeGrenade was not a BasePlayerWeapon, the fold failed for every real weapon, and EntityWrapper? was the honest type until the hierarchy landed (#30).

Skew detection

EntityWrapperRegistry.LensHash and .SchemaBuild identify the curated state these wrappers were generated from. LensHash is the hash of this repository's schema-lens/state.json under its own canonical form.

Do not compare it against a hash your own runtime computes. An implementation that maintains its own Schema Lens hashes a different preimage (different fields, different canonical form), so the two numbers are not comparable and a mismatch would be guaranteed rather than meaningful. Assert your hash against your state, and this one against the state.json this package was published beside.

What establishes compatibility across the seam is canonical path, not hash equality: two curated states can describe the same field under different spellings, and the alias tables are what reconcile them.

Versioning

1.0, and it stayed 0.x until a second implementation had run these wrappers over a real demo. The package self-verifies — every manifest passes BindingConformance, and the wrappers are exercised over the reference reader with no parser present — but a package can compile and self-verify and still be wrong. Ruling that out is what 1.0 claims.

DemoViewer.NET ran stages 2 and 3 against 0.3.0 over their own EntityTracker and a real GOTV demo: 4,434 ordinal comparisons, 0 mismatches, joined by canonical path through the alias tables, with zero adapter changes. That was the third consecutive clean round, this time across a 4.6× manifest growth. The read this package exists for was measured directly: a marker AK47 binding reads Clip1 through a base-typed BasePlayerWeapon reference, using the base's compile-time ordinal against the derived class's binding, while typeof(AK47) declares no Clip1.

The evidence has a known hole. 16 of the 59 curated classes never went live on the reference demo (CWeaponAug, CWeaponNegev, CWeaponRevolver and 13 more), so they are unexercised on real bytes; one demo cannot contain every gun. Each is a fieldless marker whose binding is its base's paths verbatim, byte-identical to markers under the same base that did sweep clean, and the prefix law is pinned structurally over all 52 derived wrappers rather than only the live ones. That is why the risk was judged small. It is not zero, and version.json carries the full list and the reasoning.

This package regenerates with the schema, so it moves when the curated state does. The contract next door deliberately does not, and the two do not share a version.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CS2OpenDev.Sdk.Entities:

Package Downloads
CS2DemoKit.Analysis

Rule-driven analysis engine for parsed Counter-Strike 2 demos: a state-graph evaluator, the shipped rulesets embedded in the assembly, per-player stats, rich highlights, and a 3D line-of-sight engine.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.6 31 9/14/2026
1.1.4 56 8/18/2026
1.1.3 995 8/18/2026