bacommon.assetspec package

Asset specs: authoring-level references to asset-package assets.

A spec is a minimal claim about an asset – an apverid plus a logical name – carrying no asset data and no guarantee the package is locally present (or still exists). Per the D28 semantic split (see strings-asset-migration.md in ballistica-internal), assets ladder through three tiers: TextureSpec (this claim form; wire/model currency) -> client bauiv1.TextureHandle (a verified-local subclass adding .get(); its wrapper pin resolved before use) -> bauiv1.Texture (the loaded engine asset). Servers hold only specs; consuming clients verify/resolve before display. Each kind gets a distinct type (TextureSpec, MeshSpec) so a consumer schema can enforce where each kind may go.

Type-safe, ergonomic access to a package’s references comes from a generated wrapper module (emitted server-side; the codegen lives in baserver.assetwrappergen), whose per-kind roots (textures, meshes) are driven at runtime by AssetGroup – mirroring the client-side asset-package wrappers.

class bacommon.assetspec.AssetBucketKind(*values)[source]

Bases: Enum

Which bucket kind a spec’s asset lives in.

Values match the asset-package bucket names. Each kind is its own flat index domain, so an index is only meaningful alongside the kind of the slot holding it – which the schema always fixes (a texture slot holds a texture).

AUDIO = 'audio'
CONSTANT = 'constant'
MESHES = 'meshes'
TEXTURES = 'textures'
class bacommon.assetspec.AssetGroup(apverid: str, node: AssetGroupTree, prefix: str)[source]

Bases: object

Dynamic accessor for one subdirectory of an asset-package’s refs.

Attribute access resolves against the wrapper’s nested kind-code tree: a subdirectory yields another AssetGroup; a leaf yields the reference for its kind. All real type information lives in the wrapper’s if TYPE_CHECKING: shadow, so callers never type-check through this class.

type bacommon.assetspec.AssetGroupTree = dict[str, str | AssetGroupTree]
class bacommon.assetspec.AssetIndexContext(packages: list[str], listings: ListingSource)[source]

Bases: object

Converts asset specs to and from flat indices for one manifest.

Built from the manifest’s package list (in index order) and a way to obtain each package’s full logical-path listing. The offset table is built on first use and cached, since obtaining a listing can be the expensive part.

describe_domain() str[source]

Per-package slice widths, for diagnosing a digest mismatch.

Names each package and how wide a slice it got here, which is what pins a mismatch to one package rather than to the payload as a whole.

domain_digest() str[source]

Short digest of the exact domain this context addresses.

The two ends derive their listings from different sources (the server from vendored package data, the client from its resolved registry), and nothing about a wrong-but-in-range index makes itself known: it simply names a different asset. So a payload carries the producer’s digest and the consumer refuses to de-index when its own does not match – turning a silent wrong-art render into a loud, locatable failure.

Taken over the effective listings, i.e. the slices actually laid out, with an unknown package contributing an empty one exactly as it does when preparing the offset table. That is the right thing to compare: a package one end has no listing for still occupies zero width on both, and it is the widths and contents that decide what an index means.

domain_size() int[source]

Total number of addressable assets across the manifest.

from_index(index: int, kind: AssetBucketKind) AnySpec[source]

Return the spec a flat index names.

kind comes from the slot holding the index and decides which spec type is produced. It is not a domain selector – there is one domain per manifest – so it cannot shift which asset an index resolves to; it only decides how that asset is typed.

Raises AssetIndexError for an index outside the domain.

property packages: list[str]

The manifest’s packages, in index order.

to_index(spec: AnySpec) int[source]

Return the flat index for a spec.

Raises AssetIndexError if the spec’s package is not in the manifest or its asset is not in that package.

exception bacommon.assetspec.AssetIndexError[source]

Bases: Exception

An asset reference could not be indexed or de-indexed.

Always an authoring or wiring fault rather than a runtime condition: a package missing from the manifest, an asset absent from its package, or an index outside its domain. Callers should fail visibly rather than substitute a placeholder – a silently wrong texture is worse than a loud error.

class bacommon.assetspec.CollisionMeshSpec(_apverid: str, _name: str)[source]

Bases: object

A language-independent reference to a collision-mesh in a package.

Identity is a package version plus the collision-mesh’s logical path within it (e.g. meshes/courtyard_level_collide); the engine resolves the qualified <apverid>:<name> form.

Both parts are private; see TextureSpec for why.

Collision meshes are a scene-only kind (physics; they ride the flavor-invariant constant bucket – asset-packages decision #26), so nothing server-side emits one. The type exists so the scene wrapper’s handle leaves stay kind-distinct like every other kind.

class bacommon.assetspec.CubeMapTextureSpec(_apverid: str, _name: str)[source]

Bases: object

A language-independent reference to a cube-map texture.

Identity is a package version plus the cube map’s logical path within it (e.g. textures/reflection_sharp); the engine resolves the qualified <apverid>:<name> form.

Both parts are private; see TextureSpec for why.

A distinct type from TextureSpec deliberately: cube maps share the 2D textures’ logical-path namespace and delivery bucket (asset-packages decision #24) but load through a different engine call producing a different texture type, and the engine’s texture registry does not type-check cache hits – so mixing the two up must be a static error at the handle tier, not a silent wrong-type asset at draw time.

class bacommon.assetspec.MeshSpec(_apverid: str, _name: str)[source]

Bases: object

A language-independent reference to a mesh in an asset-package.

Identity is a package version plus the mesh’s logical path within it (e.g. meshes/box); the engine resolves the qualified <apverid>:<name> form.

Both parts are private: a spec is produced by a generated wrapper module and consumed as a whole, and code that reaches in to rebuild a path string by hand is exactly what asset renames used to rot. Nothing accepts such a string any more (see the ap*get bindings), so reaching in has no destination.

class bacommon.assetspec.SoundSpec(_apverid: str, _name: str)[source]

Bases: object

A language-independent reference to a sound in an asset-package.

Identity is a package version plus the sound’s logical path within it (e.g. audio/swish); the engine resolves the qualified <apverid>:<name> form.

Both parts are private: a spec is produced by a generated wrapper module and consumed as a whole, and code that reaches in to rebuild a path string by hand is exactly what asset renames used to rot. Nothing accepts such a string any more (see the ap*get bindings), so reaching in has no destination.

class bacommon.assetspec.TextureSpec(_apverid: str, _name: str)[source]

Bases: object

A language-independent reference to a texture in an asset-package.

Identity is a package version plus the texture’s logical path within it (e.g. textures/zoe_icon); the engine resolves the qualified <apverid>:<name> form.

Both parts are private: a spec is produced by a generated wrapper module and consumed as a whole, and code that reaches in to rebuild a path string by hand is exactly what asset renames used to rot. Nothing accepts such a string any more (see the ap*get bindings), so reaching in has no destination.

bacommon.assetspec.spec_kind(spec: AnySpec) AssetBucketKind[source]

Return the bucket kind a spec addresses.