bacommon package

Functionality and data shared by all Ballistica components.

This includes clients, various servers, tools, etc.

Subpackages

Submodules

bacommon.analytics module

Analytics support.

class bacommon.analytics.AnalyticsEvent[source]

Bases: IOMultiType[AnalyticsEventTypeID]

Top level class for our multitype.

classmethod get_type(type_id: AnalyticsEventTypeID) type[AnalyticsEvent][source]

Return the subclass for each of our type-ids.

classmethod get_type_id() AnalyticsEventTypeID[source]

Return the type-id for this subclass.

classmethod get_type_id_storage_name() str[source]

Return the key used to store type id in serialized data.

The default is a short obscure value so that it is unlikely to conflict with members of individual type attrs, but in some cases one might prefer to serialize it to something simpler like ‘type’ by overriding this call. One just needs to make sure that no encompassed types serialize anything to that same name themself (dataclassio will error if they do).

class bacommon.analytics.AnalyticsEventTypeID(*values)[source]

Bases: Enum

Type ID for each of our subclasses.

CLASSIC = 'c'
class bacommon.analytics.ClassicAnalyticsEvent(eventtype: EventType, extra: str | None = None)[source]

Bases: AnalyticsEvent

Analytics event related to classic.

class EventType(*values)[source]

Bases: Enum

Types of classic events.

JOIN_NEARBY_PARTY = 'jn'
JOIN_PARTY_BY_ADDRESS = 'ja'
JOIN_PRIVATE_PARTY = 'jpr'
JOIN_PUBLIC_PARTY = 'jpb'
START_COOP_SESSION = 'sc'
START_FFA_SESSION = 'sf'
START_TEAMS_SESSION = 'st'
START_TOURNEY_COOP_SESSION = 'stc'
eventtype: EventType
extra: str | None = None
classmethod get_type_id() AnalyticsEventTypeID[source]

Return the type-id for this subclass.

bacommon.app module

Common high level values/functionality related to Ballistica apps.

class bacommon.app.AppArchitecture(*values)[source]

Bases: Enum

Processor architecture an app can be running on.

ARM = 'arm'
ARM64 = 'arm64'
UNKNOWN = 'unknown'
X86 = 'x86'
X86_64 = 'x86_64'
class bacommon.app.AppInterfaceIdiom(*values)[source]

Bases: Enum

A general form-factor or method of experiencing a Ballistica app.

Note that it may be possible for a running app to switch idioms (for instance if a mobile device or computer is connected to a TV).

CONSOLE = 'console'

Screen with medium amount of detail visible; assumed to have game controller(s) as primary input. Note that this covers handheld or arcade cabinet scenarios in addition to tv-connected consoles.

DESKTOP = 'desktop'

Screen with high amount of detail visible; assumed to have keyboard/mouse as primary input.

HEADLESS = 'headless'

The app has no interface (generally is acting as a server).

PHONE = 'phone'

Small screen; assumed to have touch as primary input.

TABLET = 'tablet'

Medium size screen; assumed to have touch as primary input.

XR_HEADSET = 'xr_headset'

Displayed over or in place of of the real world on a headset; assumed to have hand tracking or spatial controllers as primary input.

XR_PHONE = 'xr_phone'

Displayed over or instead of the real world on a small screen; assumed to have device movement augmented by physical or touchscreen controls as primary input.

XR_TABLET = 'xr_tablet'

Displayed over or instead of the real world on a medium size screen; assumed to have device movement augmented by physical or touchscreen controls as primary input.

class bacommon.app.AppPlatform(*values)[source]

Bases: Enum

Overall platform a build can target.

Each distinct flavor of an app has a unique combination of AppPlatform and AppVariant. Generally platform describes a set of hardware, while variant describes a destination or purpose for the build.

ANDROID = 'android'
IOS = 'ios'
LINUX = 'linux'
MACOS = 'macos'
TVOS = 'tvos'
UNKNOWN = 'unknown'
WINDOWS = 'windows'
class bacommon.app.AppVariant(*values)[source]

Bases: Enum

A unique Ballistica build variation within a single platform.

Each distinct permutation of an app has a unique combination of AppPlatform and AppVariant. Generally platform describes a set of hardware, while variant describes a destination or purpose for the build.

AMAZON_APPSTORE = 'amazon_appstore'
APPLE_APP_STORE = 'apple_app_store'
ARCADE = 'arcade'
CARDBOARD = 'cardboard'
DEMO = 'demo'
EPIC_GAMES_STORE = 'epic_games_store'
GENERIC = 'generic'

Default builds.

GOOGLE_PLAY = 'google_play'
META = 'meta'
STEAM = 'steam'
TEST_BUILD = 'test_build'

Particular builds intended for public testing (may have some extra checks or logging enabled).

WINDOWS_STORE = 'windows_store'
class bacommon.app.ExitCode(*values)[source]

Bases: Enum

Process exit codes a Ballistica app may return on a clean shutdown.

These accompany a clean (non-crash) shutdown: the app tears down gracefully via the normal shutdown sequence and simply returns one of these as its process exit code. This is distinct from a fatal-error/abort exit (which routes through crash reporting); a nonzero value here means “we shut down cleanly, but the run was a failure”. Supervisors such as the headless server-wrapper script (and BASN task orchestration) interpret nonzero values to decide whether restarting is worthwhile.

ASSET_BRINGUP_FAILED = 10

A headless app could not bring up assets it requires to run (e.g. a construct-mode asset resolve failed terminally). Not transient — a restart with the same inputs would fail the same way — so supervisors should treat it as terminal and not auto-restart.

SUCCESS = 0

Normal successful exit.

bacommon.assetcas module

Shared content-addressed asset-blob download primitive.

Used by both the game client’s asset subsystem and the bacloud CLI to fetch content-addressed blobs from a basn node’s /casblob endpoint, verify them, and write them into a local content-addressed cache. The logic here is engine-agnostic: the caller supplies the urllib3 pool, the node base URL, the encoded capability token, and the destination cache root, and owns concurrency, retry, and progress reporting itself.

exception bacommon.assetcas.CasDownloadError[source]

Bases: Exception

A CAS-blob fetch, hash-verify, or write failed.

bacommon.assetcas.blob_present(roots: list[str], filehash: str, size: int) bool[source]

Is this CAS blob already present at the expected size in any root?

Present-but-wrong-size counts as absent (so it gets refetched and overwritten). The free st_size check is a cheap catch for external truncation or tampering, not a content proof.

bacommon.assetcas.cas_blob_path(root: str, filehash: str) str[source]

Return the path a CAS blob occupies under a cache root.

Blobs are sharded 256 ways by the first two hex chars of the hash to keep per-directory counts low for cache scanning.

bacommon.assetcas.cas_write(dest_root: str, filehash: str, data: bytes) None[source]

sha256-verify data then atomically write it into dest_root.

Verify, write to a temp file in the destination directory, fsync, then os.replace (atomic on the same filesystem). A file at its CAS path is therefore always whole-and-correct: a crash mid-write leaves only a temp file, never a partial blob at the final path. Raises CasDownloadError on a hash mismatch.

bacommon.assetcas.download_cas_blob(pool: PoolManager, base_url: str, filehash: str, size: int, *, token_header: str, dest_root: str, timeout_seconds: float) None[source]

Fetch one CAS blob from a node and atomically write it.

Issues GET {base_url}/casblob/{filehash}?size={size} with the capability token in the X-Asset-Token header and an X-Accept-Compression header advertising every encoding this build can decode, so the node fulfills the request in whichever supported encoding is smallest/available. filehash and size are the blob’s canonical (uncompressed) identity. The encoding of the received bytes is taken solely from the node’s X-Cas-Compression response header – it is authoritative, so whatever the node actually served is decoded correctly regardless of how it was cached. (The flavor-manifest no longer carries compression; encoding is purely a transfer-layer concern negotiated here.) The bytes are decompressed back to canonical before cas_write() sha256-verifies them against filehash and writes them – so the local cache always holds uncompressed blobs and the hash check still validates content. One attempt only; the caller owns concurrency and retry. Raises CasDownloadError on a non-200 response, a missing/unknown served-encoding header, a decompress failure, or a verify failure.

bacommon.assetcas.encode_asset_token(token: securedata.Archive) str[source]

Encode a capability token for the X-Asset-Token header.

Returns base64-urlsafe of the token’s canonical JSON (HTTP headers don’t carry raw JSON cleanly).

bacommon.bacloud module

Functionality related to the bacloud tool.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

bacommon.build module

Functionality related to game builds.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

bacommon.clienteffect module

ClientEffect related functionality.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

bacommon.cloud module

Cloud related functionality.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

bacommon.cloudfilecodec module

zstd (de)compression for cloud-file blobs.

Holds the CompressionType enum (how a stored blob is encoded) plus the codec dispatch mapping each type to a concrete zstd operation – including which pre-shared dictionary a dict-based type uses. Lives in bacommon so both server components (which compress on store) and client components (which read the compression type from an asset manifest and decompress on load) share one source of truth for the type->codec mapping and load identical dictionary bytes.

Compression is always expressed relative to a cloud-file’s canonical (uncompressed) content: a blob’s stored bytes are the canonical content run through its CompressionType. UNCOMPRESSED means the stored bytes are the canonical content.

bacommon.cloudfilecodec.CAS_ACCEPT_COMPRESSION_HEADER = 'X-Accept-Compression'

a comma-separated list of CompressionType values the client can decode (e.g. 'zb1,z,u'). Lets the server fulfill the request in any encoding the client supports – picking the smallest available – rather than assuming a fixed one. A client always includes u (it can always handle uncompressed). Absent ⇒ a legacy client that decodes per its manifest (back-compat).

Type:

Request header on a CAS /casblob GET

bacommon.cloudfilecodec.CAS_COMPRESSION_HEADER = 'X-Cas-Compression'

the single CompressionType value the served bytes are encoded in. The client decodes per THIS header (not its manifest), so whatever the node actually serves – even a re-negotiated or differently-cached encoding – is always decoded correctly. Absent ⇒ a legacy node; the client falls back to its manifest’s compression.

Type:

Response header on a CAS /casblob GET

class bacommon.cloudfilecodec.CompressionType(*values)[source]

Bases: Enum

How a blob’s stored bytes are compressed.

New members can be added as we see fit. Each member must encode enough to decompress deterministically – dictionary-based members bake in the exact dictionary and version, so a stored blob is always decodable by whatever holds that member. The enum value is a stable wire string: it travels in asset manifests for the client to read, so values must never change.

UNCOMPRESSED = 'u'

Stored bytes are the canonical content verbatim.

ZSTD = 'z'

Stored bytes are the canonical content run through plain zstd.

ZSTD_DICT_BOB_V1 = 'zb1'

Stored bytes are the canonical content run through zstd using the v1 display-mesh (.bob) dictionary (bacommon.meshzstddict.display_mesh_dict_v1()). The dict and its version are fixed by this member.

bacommon.cloudfilecodec.all_compression_types() set[CompressionType][source]

All CompressionType members this build can decode.

What a client advertises in CAS_ACCEPT_COMPRESSION_HEADER – every member is decodable here because the codecs (and any bundled dictionaries) ship with the build.

bacommon.cloudfilecodec.compress_for_type(canonical: bytes, ctype: CompressionType, *, level: int) bytes[source]

Encode canonical bytes per ctype.

The canonical (uncompressed) content identified by a cloud-file’s cloud_file_id. Inverse of decompress_for_type(). This is the single source of truth for which dictionary a dict-based type uses.

bacommon.cloudfilecodec.decompress_for_type(stored: bytes, ctype: CompressionType) bytes[source]

Decode stored bytes per ctype back to canonical content.

Inverse of compress_for_type().

bacommon.cloudfilecodec.format_compression_accept(types: Iterable[CompressionType]) str[source]

Encode a set of supported types for the accept header.

bacommon.cloudfilecodec.parse_compression_accept(value: str | None) set[CompressionType][source]

Decode the accept header into a set of known types.

Tolerant of unknown/empty entries (an unrecognized future value is skipped, not an error) so an old server reading a newer client’s header simply ignores types it doesn’t know.

bacommon.cloudfilecodec.zstd_compress(data: bytes, level: int) bytes[source]

Compress data with plain zstd at the given level.

bacommon.cloudfilecodec.zstd_compress_with_dict(data: bytes, dict_bytes: bytes, level: int) bytes[source]

Compress data with zstd using a pre-shared dictionary.

bacommon.cloudfilecodec.zstd_decompress(data: bytes) bytes[source]

Decompress plain-zstd data produced by zstd_compress().

bacommon.cloudfilecodec.zstd_decompress_with_dict(data: bytes, dict_bytes: bytes) bytes[source]

Decompress dict-zstd data using its pre-shared dictionary.

dict_bytes must be the exact dictionary used to compress data; a mismatch raises or yields garbage.

bacommon.displayitem module

Functionality for displaying currencies, prizes, owned items, etc.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

bacommon.locale module

Functionality for wrangling locale info.

class bacommon.locale.Locale(*values)[source]

Bases: Enum

A distinct grouping of language, cultural norms, etc.

This list of locales is considered ‘sacred’ - we assume any values (and associated long values) added here remain in use out in the wild indefinitely. If a locale is superseded by a newer or more specific one, the new locale should be added and both new and old should map to the same LocaleResolved.

ARABIC = 'arabc'
BELARUSSIAN = 'blrs'
CHINESE = 'chn'
CHINESE_SIMPLIFIED = 'chn_sim'
CHINESE_TRADITIONAL = 'chn_tr'
CROATIAN = 'croat'
CZECH = 'czch'
DANISH = 'dnsh'
DUTCH = 'dtch'
ENGLISH = 'eng'
ESPERANTO = 'esprnto'
FILIPINO = 'filp'
FRENCH = 'frnch'
GERMAN = 'grmn'
GIBBERISH = 'gibber'
GREEK = 'greek'
HINDI = 'hndi'
HUNGARIAN = 'hngr'
INDONESIAN = 'indnsn'
ITALIAN = 'italn'
JAPANESE = 'jpn'
KAZAKH = 'kazk'
KOREAN = 'kor'
MALAY = 'mlay'
PERSIAN = 'pers'
PIRATE_SPEAK = 'pirate'
POLISH = 'pol'
PORTUGUESE = 'prtg'
PORTUGUESE_BRAZIL = 'prtg_brz'
PORTUGUESE_PORTUGAL = 'prtg_pr'
ROMANIAN = 'rom'
RUSSIAN = 'rusn'
SERBIAN = 'srbn'
SLOVAK = 'slvk'
SPANISH = 'spn'
SPANISH_LATIN_AMERICA = 'spn_lat'
SPANISH_SPAIN = 'spn_spn'
SWEDISH = 'swed'
TAMIL = 'taml'
THAI = 'thai'
TURKISH = 'turk'
UKRAINIAN = 'ukrn'
VENETIAN = 'venetn'
VIETNAMESE = 'viet'
property description: str[source]

A human readable description for the locale.

Intended as instructions to humans or AI for translating. For most locales this is simply the language name, but for special ones like pirate-speak it may include instructions.

classmethod from_long_value(value: str) Locale[source]

Given a long value, return a Locale.

property long_value: str[source]

A longer more human readable alternative to value.

Like the regular enum values, these values will never change and can be used for persistent storage/etc.

property resolved: LocaleResolved[source]

Return the associated resolved locale.

class bacommon.locale.LocaleResolved(*values)[source]

Bases: Enum

A resolved Locale for use in logic.

These values should never be stored or transmitted and should always come from resolving a Locale which can be stored/transmitted. This gives us the freedom to revise this list as needed to keep our actual list of implemented resolved-locales as trim as possible.

ARABIC = 'arabc'
BELARUSSIAN = 'blrs'
CHINESE_SIMPLIFIED = 'chn_sim'
CHINESE_TRADITIONAL = 'chn_tr'
CROATIAN = 'croat'
CZECH = 'czch'
DANISH = 'dnsh'
DUTCH = 'dtch'
ENGLISH = 'eng'
ESPERANTO = 'esprnto'
FILIPINO = 'filp'
FRENCH = 'frnch'
GERMAN = 'grmn'
GIBBERISH = 'gibber'
GREEK = 'greek'
HINDI = 'hndi'
HUNGARIAN = 'hngr'
INDONESIAN = 'indnsn'
ITALIAN = 'italn'
JAPANESE = 'jpn'
KAZAKH = 'kazk'
KOREAN = 'kor'
MALAY = 'mlay'
PERSIAN = 'pers'
PIRATE_SPEAK = 'pirate'
POLISH = 'pol'
PORTUGUESE_BRAZIL = 'prtg_brz'
PORTUGUESE_PORTUGAL = 'prtg_pr'
ROMANIAN = 'rom'
RUSSIAN = 'rusn'
SERBIAN = 'srbn'
SLOVAK = 'slvk'
SPANISH_LATIN_AMERICA = 'spn_lat'
SPANISH_SPAIN = 'spn_spn'
SWEDISH = 'swed'
TAMIL = 'taml'
THAI = 'thai'
TURKISH = 'turk'
UKRAINIAN = 'ukrn'
VENETIAN = 'venetn'
VIETNAMESE = 'viet'
static from_tag(tag: str) LocaleResolved[source]

Return a locale for a given string tag.

Tags can be provided in BCP 47 form (‘en-US’) or POSIX locale string form (‘en_US.UTF-8’).

property locale: Locale[source]

Return a locale that resolves to this resolved locale.

In some cases, such as when presenting locale options to the user, it makes sense to iterate over resolved locale values, as regular locales may include obsolete or redundant values. When storing locale values to disk or transmitting them, however, it is important to use plain locales. This method can be used to get back to a plain locale from a resolved one.

property tag: str[source]

An IETF BCP 47 tag for this locale.

This is often simply a language code (‘en’) but may in some cases include the country (‘pt-BR’) or script (‘zh-Hans’). Locales which are not “real” will include an ‘x’ in the middle (‘en-x-pirate’).

bacommon.loctext module

Localized-text evaluation (structured plural/select + substitutions).

The value delivered for a locale is either a plain str or a StringSelector. evaluate() resolves either against a locale and caller-supplied arguments to a final str:

  • A plain str has its {name} placeholders replaced by str(args['name']).

  • A StringSelector picks one leaf string at render time – by CLDR plural category of an integer arg (kind=PLURAL) or by the string value of an arg (kind=SELECT) – then substitutes {name} and (for plurals) # (the count) in the chosen leaf.

We use our own small structured form rather than parsing a message-format string: the runtime decisions are few and closed, the data is self-describing (it can only express what we support), and a struct is far safer to port than a hand-rolled string parser. This is the prototype of the native Lstr2 runtime – keeping it in bacommon gives pure-Python consumers (the client’s embedded Python, server-side resolution in bamaster/basn) the same evaluator, and the StringSelector IOAttrs keys are the on-the-wire schema the C++ port implements.

Scope: cardinal plural selection for non-negative integers, string select, {name} substitution, and # for the plural count. =N keys match an exact count before the category rule. Not (yet): ordinals/selectordinal, decimal operands, plural offsets, nested selectors.

exception bacommon.loctext.LocTextError[source]

Bases: Exception

A malformed localized value or a missing/bad argument.

class bacommon.loctext.PluralCategory(*values)[source]

Bases: Enum

A CLDR plural category.

FEW = 'few'
MANY = 'many'
ONE = 'one'
OTHER = 'other'
TWO = 'two'
ZERO = 'zero'
class bacommon.loctext.SelectorKind(*values)[source]

Bases: Enum

How a StringSelector chooses among its forms.

PLURAL = 'p'

Choose by the CLDR plural category of an integer argument.

SELECT = 's'

Choose by the string value of an argument (e.g. gender).

class bacommon.loctext.StringSelector(kind: SelectorKind, arg: str, forms: dict[str, str])[source]

Bases: object

A localized string whose final form is chosen at render time.

The structured alternative to a plain str value: forms maps a form key to its leaf text (which may carry {name} substitutions and, for plurals, # for the count). For PLURAL the keys are CLDR category names (one/few/…/other) or =N exact-count matches; for SELECT they are the possible string values of arg. other is the fallback. The IOAttrs keys are the on-the-wire schema shared with the native Lstr2 port.

arg: str
forms: dict[str, str]
kind: SelectorKind
bacommon.loctext.evaluate(value: str | StringSelector, locale: Locale, **args: object) str[source]

Resolve a localized value to a final string.

value is a plain str ({name} substitutions only) or a StringSelector (plural/select choice, then substitution). args supplies the named arguments; plural selection uses locale’s CLDR rule (see plural_category()). Raises LocTextError on a missing/ill-typed argument or a selector with no matching form and no other.

bacommon.loctext.plural_category(locale: Locale, n: int) PluralCategory[source]

Return the CLDR cardinal plural category for an integer in a locale.

n is treated as a non-negative integer count (its absolute value is used). Uses the locale’s resolved form so obsolete aliases (e.g. the bare SPANISH) follow their modern locale’s rule. Every shipped locale is mapped explicitly; a genuinely-unknown locale falls back to the one-for-1 rule. See the module note for the integer-only scope.

bacommon.loctext.required_plural_categories(locale: Locale) list[PluralCategory][source]

The plural categories a plural message must cover for a locale.

The set the locale’s rule can produce over the integers, plus the mandatory other fallback (ICU requires it), in canonical order. Used to tell a translation model exactly which plural forms to emit for the target locale – derived from the same rules plural_category() evaluates with, so producer and client agree.

The integer sample (0..200) spans every modulo cycle the rules use; under the integer-only scope (see the module note) this is the exact set of categories the locale can select.

bacommon.loggercontrol module

System for managing loggers.

class bacommon.loggercontrol.LoggerControlConfig(levels: dict[str, int]=<factory>)[source]

Bases: object

A logging level configuration that applies to all loggers.

Any loggers not explicitly contained in the configuration will be set to NOTSET.

apply(*, warn_unexpected_loggers: bool = False, warn_missing_loggers: bool = False, ignore_log_prefixes: list[str] | None = None) None[source]

Apply the config to all Python loggers.

If ‘warn_unexpected_loggers’ is True, warnings will be issues for any loggers not explicitly covered by the config. This is useful to help ensure controls for all possible loggers are present in a UI/etc.

If ‘warn_missing_loggers’ is True, warnings will be issued for any loggers present in the config that are not found at apply time. This can be useful for pruning settings for no longer used loggers.

Warnings for any log names beginning with any strings in ‘ignore_log_prefixes’ will be suppressed. This can allow ignoring loggers associated with submodules for a given package and instead presenting only a top level logger (or none at all).

apply_diff(diffconfig: LoggerControlConfig) LoggerControlConfig[source]

Apply a diff config to ourself.

Note that values that resolve to NOTSET are left intact in the output config. This is so all loggers expected by either the base or diff config to exist can be created if desired/etc.

diff(baseconfig: LoggerControlConfig) LoggerControlConfig[source]

Return a config containing only changes compared to a base config.

Note that this omits all NOTSET values that resolve to NOTSET in the base config.

This diffed config can later be used with apply_diff() against the base config to recreate the state represented by self.

classmethod from_current_loggers() Self[source]

Build a config from the current set of loggers.

get_effective_level(logname: str) int[source]

Given a log name, predict its level if this config is applied.

levels: dict[str, int]
sanity_check_effective_levels() None[source]

Checks existing loggers to make sure they line up with us.

This can be called periodically to ensure that a control-config is properly driving log levels and that nothing else is changing them behind our back.

would_make_changes() bool[source]

Return whether calling apply would change anything.

bacommon.logging module

Logging functionality.

class bacommon.logging.ClientLoggerName(*values)[source]

Bases: Enum

Logger names used on the Ballistica client.

ACCOUNT = 'ba.account'
ACCOUNT_CLIENT_V2 = 'ba.accountclientv2'
APP = 'ba.app'
ASSETS = 'ba.assets'
ASSET_MANAGER = 'ba.assetmanager'
AUDIO = 'ba.audio'
BA = 'ba'
CACHE = 'ba.cache'
CLOUD_SUBSCRIPTION = 'ba.cloudsub'
CONNECTIVITY = 'ba.connectivity'
DISCORD = 'ba.discord'
DISPLAYTIME = 'ba.displaytime'
ENV = 'ba.env'
GARBAGE_COLLECTION = 'ba.gc'
GRAPHICS = 'ba.gfx'
INPUT = 'ba.input'
LIFECYCLE = 'ba.lifecycle'
LOGIN_ADAPTER = 'ba.loginadapter'
NETWORKING = 'ba.net'
PERFORMANCE = 'ba.perf'
UI = 'ba.ui'
V2TRANSPORT = 'ba.v2transport'
WORKSPACE = 'ba.workspace'
property description: str

Return a short description for the logger.

bacommon.logging.get_base_logger_control_config_client() LoggerControlConfig[source]

Return the logger-control-config used by the Ballistica client.

This should remain consistent since local logger configurations are stored relative to this.

bacommon.login module

Functionality related to cloud based assets.

class bacommon.login.LoginType(*values)[source]

Bases: Enum

Types of logins available.

DISCORD = 'discord'

Discord (OAuth2 via Discord Social SDK)

EMAIL = 'email'

Email/password

GAME_CENTER = 'game_center'

Apple’s Game Center

GPGS = 'gpgs'

Google Play Game Services

property displayname: str

A human readable name for this value.

property displaynameshort: str

A short human readable name for this value.

bacommon.meshzstddict module

Zstandard dictionaries for compressing display-mesh (.bob) data.

These dictionaries are trained on the corpus of display-mesh blobs and substantially improve zstd compression of individual meshes (small files that share a lot of structure). They live in bacommon so that both server components (which compress meshes on store) and client components (which decompress them on load) load identical dictionary bytes.

Dictionaries are immutable and versioned: never mutate an existing .dict file – add a new version instead, so blobs compressed with an older dictionary remain decompressable. Whatever stores a compressed blob must record which dictionary version produced it.

bacommon.meshzstddict.display_mesh_dict_v1() bytes[source]

Return the v1 zstd dictionary for display-mesh (.bob) data.

The dictionary is read from the bundled data file on first call and cached for the lifetime of the process.

bacommon.metascan module

Scanner for # ba_meta directives in Python source.

Recognized directive shapes:

  • # ba_meta require api <N> — module-level API version requirement. When expected_api_version is supplied, modules whose value doesn’t match are skipped (and listed in ScanResults.incorrect_api_modules). When it is None the line is parsed for validity but no filtering occurs.

  • # ba_meta export <TYPE> — export the class defined on the next non-blank source line under the export-type <TYPE>.

  • # ba_meta require asset-package <ID> — module declares that it needs the named asset-package at runtime.

Other shapes are reported as malformed.

This module has no dependencies beyond the standard library so it can run anywhere — in the game runtime (wrapped by babase._meta.MetadataSubsystem), in build/tooling contexts (e.g. tools/pcommand assetpins), or in tests.

Higher-level concerns — background-thread scheduling, UI feedback, expansion of legacy export-type shortcuts, deprecation warnings — live in the consumer.

class bacommon.metascan.DirectoryScan(paths: list[str], expected_api_version: int | None = None, deprecated_export_shortcuts: dict[str, str] | None = None, *, expects_extras: bool = False)[source]

Bases: object

Scans directories for # ba_meta directives.

Pure-Python; no runtime dependencies. Construct with a list of paths (which must already be on PYTHONPATH if discovered modules will be imported by the consumer), then call run(). Results land in results.

If expected_api_version is set, modules whose # ba_meta require api value doesn’t match are skipped and listed in ScanResults.incorrect_api_modules. Pass None to scan regardless of api version (the typical tooling-side mode).

If deprecated_export_shortcuts is provided, export-type strings that appear as keys are substituted with the corresponding canonical class path and a deprecation warning is emitted with file:line context. Pass None to perform no substitution.

If expects_extras is True, run() will block after finishing the base paths until set_extras() is called from another thread. This supports the runtime pattern of kicking off the scan immediately and providing extra paths (workspace dirs, etc.) once they are known. Synchronous tooling callers should leave it at the default False; run() will then finish after the base paths.

run() None[source]

Do the thing.

set_extras(paths: list[str]) None[source]

Set extra portion.

class bacommon.metascan.ScanResults(exports: dict[str, list[str]]=<factory>, asset_packages: dict[str, list[str]]=<factory>, incorrect_api_modules: list[str] = <factory>, announce_errors_occurred: bool = False)[source]

Bases: object

Final results from a meta-scan.

announce_errors_occurred: bool = False
asset_packages: dict[str, list[str]]
exports: dict[str, list[str]]
exports_by_name(name: str) list[str][source]

Return exports matching a given name.

incorrect_api_modules: list[str]

bacommon.net module

Network related data and functionality.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

bacommon.securedata module

Functionality related to verifying server generated data.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.

class bacommon.securedata.Archive(data: bytes, signature: bytes, cert: Cert | None = None)[source]

Bases: object

Self-contained signed payload — verifiable on its own.

Produced by Writer.write() (delegate-signed) or make_master_archive() (master-signed; cert is None). Verified via Reader.read(), which returns the payload bytes on success.

Embed directly as a field on dataclassio messages and responses (Annotated[Archive, IOAttrs('x')]); dataclassio nests the bytes-typed fields into the outer JSON without the extra base64-of-base64 round trip a bytes-blob field would incur. For the rare case that genuinely needs bytes (filesystem storage, etc.), serialize explicitly with efro.dataclassio.dataclass_to_json().

Per-field IOAttrs short keys are stable; new optional fields get soft_default rather than reusing or renaming existing ones — same as anywhere else dataclassio types travel.

cert: Cert | None = None
data: bytes
signature: bytes
class bacommon.securedata.Cert(payload: bytes, signature: bytes)[source]

Bases: object

Master-signed delegation certificate.

Asserts that the public key embedded in payload is authorized to sign on the master’s behalf for the validity window in the payload. Verifiable by anyone holding a current Reader (server-side) or the static public keys baked into the client binary (client-side, once a future client version supports the cert path).

Carries an opaque payload (canonical JSON of CertPayload) plus the master signature over those bytes. Mirrors the InsecureDirective pattern — re-encoding the dataclass on every verify would risk canonicalization drift.

decoded_payload() CertPayload[source]

Decode payload to a CertPayload.

Does not verify the signature. Callers are expected to be operating on a cert that’s already been chain-verified via Reader.check().

payload: bytes

Canonical JSON encoding of CertPayload. Signed as-is and re-decoded by recipients via decoded_payload().

signature: bytes

Signature of payload made with the master signing key.

class bacommon.securedata.CertPayload(publickey: bytes, starttime: datetime, endtime: datetime, issuer: str)[source]

Bases: object

Unsigned content of a Cert.

Master signs the canonical JSON encoding of this; recipients re-decode after signature verification to access the fields.

endtime: datetime
issuer: str

Identifier of the delegate (e.g. basn node id) for accountability and debugging. Not used for verification.

publickey: bytes

Public half of the keypair this cert authorizes.

starttime: datetime

Validity window during which signatures made by the matching private key should be honored.

exception bacommon.securedata.Invalid[source]

Bases: Exception

Raised by Reader.read() and Writer.write() when an operation cannot complete safely.

For read: the archive bytes are malformed, the master signature does not validate, the cert is expired or signed by an unknown master, or the delegate signature does not match.

For write: the writer’s cert is past its validity window and would produce a signed archive a verifier would refuse.

str(exc) carries a short reason suitable for logging.

bacommon.securedata.MIN_CLIENT_BUILD_FOR_DELEGATE_VERIFY: int = 22843

Minimum ballistica-internal client build number known to support cert-bearing verification via Reader.check(). Older clients only have the legacy check(data, signature) path and would silently fail to verify (data, signature, cert) triples — server code must therefore skip the delegate-signed path for clients reporting a build below this threshold.

Bump in lockstep with the ballistica-internal release that ships the matching client code. Tracks the build at the moment the cert path was added; ratchets upward only when a protocol change requires it.

class bacommon.securedata.Reader(starttime: datetime, endtime: datetime, publickeys: list[bytes])[source]

Bases: object

Verifies data as being signed by our master server.

check(data: bytes, signature: bytes, cert: Cert | None = None) bool[source]

Verify data, returning True if successful.

When cert is None (the default), signature is verified directly against the master public keys — used for anything bamaster signed itself.

When cert is given, the chain is verified end-to-end:

  1. The cert’s signature must validate against a master public key (proving the master authorized this delegate).

  2. The cert’s validity window must include now.

  3. signature must then validate against the delegate public key embedded in the cert.

Used for data signed by a delegate (typically a basn node) acting on the master’s behalf. Older clients that predate the delegate-signing rollout will only ever be sent cert=None triples; see bacommon/securedata.py design notes for the rollout discipline.

Uses the ballistica native _babase.verify_ed25519 when available (inside the app binary). On contexts without it (server, pytest, tools scripts) falls back to the cryptography package, which those contexts already have.

endtime: datetime
publickeys: list[bytes]
read(archive: Archive) bytes[source]

Verify archive and return its data on success.

archive is the Archive produced by Writer.write() or make_master_archive(). Routes automatically: an archive without a cert is verified directly against this reader’s master public keys; an archive with a cert is chain-verified (cert against master keys, then data against the cert’s delegate pubkey).

Raises Invalid for any failure — expired/forged cert, bad signature.

starttime: datetime
bacommon.securedata.STATIC_DATA_PUBLIC_KEYS: tuple[bytes, ...] = (b'\x98\xc0\xb3{\xea\n\t\x0f\xfb\xcbN\x1c\x03A\xd7\xd6d\x95{.\xdc\xda\x9b\xf6\xe0\x7f\x0bM\x84^\x15b',)

Public halves of long-lived Ed25519 keypairs the master server uses to sign static data that clients must verify offline — without any network round trip to fetch a Reader. Compiled into the client binary, so a MITM cannot swap them.

Multiple entries let us rotate: add the new key (as a second entry) in a client release, wait for that release to propagate to the installed base, switch the server to sign with the new key, then later drop the old key in a subsequent client release. On verify, a signature is considered valid if ANY listed key verifies it.

The matching private halves live on the master server in svals under static_data_private_key (current signing key). basn nodes fetch the current private key from the master at startup over the secure inter-node channel.

class bacommon.securedata.Writer(starttime: datetime, endtime: datetime, privatekey: bytes, cert: Cert)[source]

Bases: object

Delegated signing capability.

Issued by the master to a delegate (typically a basn node) over a secure channel. The delegate calls write() on data and distributes the resulting archive bytes; recipients chain-verify and recover the original bytes via Reader.read().

Sensitive: contains a private key. Never traverses untrusted channels and should not be persisted to disk.

The top-level starttime / endtime mirror the cert’s validity window for ergonomic refresh-decision code (see basn’s _update_secure_data_signer). They are NOT authoritative for verification — cert is.

Servers must consult MIN_CLIENT_BUILD_FOR_DELEGATE_VERIFY before sending delegate-signed archives to a client; clients below the threshold do not understand the cert path and would reject the archive.

cert: Cert

Master-signed cert authorizing the public counterpart of privatekey.

endtime: datetime
privatekey: bytes

Raw Ed25519 private key bytes.

sign(data: bytes) bytes[source]

Low-level: produce a raw signature over data.

Most callers should use write() instead — it returns a self-contained archive that includes the signature, the cert, and the data, all verifiable in a single Reader.read() call.

starttime: datetime
write(data: bytes) Archive[source]

Sign data and return an Archive.

The archive carries the signature + the writer’s cert; recipients pass it to Reader.read() to verify and recover the original bytes.

Raises Invalid if the writer’s cert has expired — an archive produced past expiry would be rejected by every verifier, so we fail fast at write time rather than at verify time.

bacommon.securedata.make_master_archive(data: bytes, master_priv_bytes: bytes) Archive[source]

Pack data + a master-key signature into an Archive.

Server-only — clients do not have access to a master private key. Verifiers (with master public keys) accept the resulting archive via Reader.read() exactly the same way they accept Writer-produced archives, except the master path carries no cert.

master_priv_bytes is the raw 32-byte Ed25519 seed. On bamaster, prefer UniversalGlobals.secure_data_archive_master which sources the key from svals.

bacommon.servermanager module

Functionality related to the server manager script.

class bacommon.servermanager.ChatMessageCommand(message: str, clients: list[int] | None)[source]

Bases: ServerCommand

Chat message from the server.

clients: list[int] | None
message: str
class bacommon.servermanager.ClientListCommand[source]

Bases: ServerCommand

Print a list of clients.

class bacommon.servermanager.KickCommand(client_id: int, ban_time: int | None)[source]

Bases: ServerCommand

Kick a client.

ban_time: int | None
client_id: int
class bacommon.servermanager.ScreenMessageCommand(message: str, color: tuple[float, float, float] | None, clients: list[int] | None)[source]

Bases: ServerCommand

Screen-message from the server.

clients: list[int] | None
color: tuple[float, float, float] | None
message: str
class bacommon.servermanager.ServerCommand[source]

Bases: object

Base class for commands that can be sent to the server.

class bacommon.servermanager.ServerConfig(party_name: str = 'FFA', party_is_public: bool = True, authenticate_clients: bool = True, admins: list[str] = <factory>, enable_default_kick_voting: bool = True, public_ipv4_address: str | None = None, public_ipv6_address: str | None = None, port: int = 43210, max_party_size: int = 6, session_max_players_override: int | None = None, session_type: str = 'ffa', playlist_code: int | None = None, playlist_inline: list[dict[str, ~typing.Any]] | None = None, playlist_shuffle: bool = True, auto_balance_teams: bool = True, coop_campaign: str = 'Easy', coop_level: str = 'Onslaught Training', enable_telnet: bool = False, teams_series_length: int = 7, ffa_series_length: int = 24, stats_url: str | None = None, clean_exit_minutes: float | None = None, unclean_exit_minutes: float | None = None, idle_exit_minutes: float | None = None, show_tutorial: bool = False, team_names: tuple[str, str] | None = None, team_colors: tuple[tuple[float, float, float], tuple[float, float, float]] | None = None, enable_queue: bool = True, protocol_version: int | None = None, stress_test_players: int | None = None, player_rejoin_cooldown: float = 10.0, log_levels: dict[str, str] | None = None, dont_write_bytecode: bool = False)[source]

Bases: object

Configuration for the server manager app (<appname>_server).

admins: list[str]
authenticate_clients: bool = True
auto_balance_teams: bool = True
clean_exit_minutes: float | None = None
coop_campaign: str = 'Easy'
coop_level: str = 'Onslaught Training'
dont_write_bytecode: bool = False
enable_default_kick_voting: bool = True
enable_queue: bool = True
enable_telnet: bool = False
ffa_series_length: int = 24
idle_exit_minutes: float | None = None
log_levels: dict[str, str] | None = None
max_party_size: int = 6
party_is_public: bool = True
party_name: str = 'FFA'
player_rejoin_cooldown: float = 10.0
playlist_code: int | None = None
playlist_inline: list[dict[str, Any]] | None = None
playlist_shuffle: bool = True
port: int = 43210
protocol_version: int | None = None
public_ipv4_address: str | None = None
public_ipv6_address: str | None = None
session_max_players_override: int | None = None
session_type: str = 'ffa'
show_tutorial: bool = False
stats_url: str | None = None
stress_test_players: int | None = None
team_colors: tuple[tuple[float, float, float], tuple[float, float, float]] | None = None
team_names: tuple[str, str] | None = None
teams_series_length: int = 7
unclean_exit_minutes: float | None = None
class bacommon.servermanager.ShutdownCommand(reason: ShutdownReason, immediate: bool)[source]

Bases: ServerCommand

Tells the server to shut down.

immediate: bool
reason: ShutdownReason
class bacommon.servermanager.ShutdownReason(*values)[source]

Bases: Enum

Reason a server is shutting down.

NONE = 'none'
RESTARTING = 'restarting'
class bacommon.servermanager.StartServerModeCommand(config: ServerConfig)[source]

Bases: ServerCommand

Tells the app to switch into ‘server’ mode.

config: ServerConfig

bacommon.text module

Text related bits.

class bacommon.text.SpecialChar(*values)[source]

Bases: Enum

Custom unicode characters the engine can display.

Keep this in sync with babase._generated.enums.SpecialChar.

BACK = '\ue00b'
BOTTOM_BUTTON = '\ue008'
BOXING_GLOVE = '\ue067'
CLOSE = '\ue017'
CROWN = '\ue043'
DELETE = '\ue009'
DICE_BUTTON1 = '\ue022'
DICE_BUTTON2 = '\ue023'
DICE_BUTTON3 = '\ue024'
DICE_BUTTON4 = '\ue025'
DOWN_ARROW = '\ue004'
DPAD_CENTER_BUTTON = '\ue010'
DRAGON = '\ue048'
EYE_BALL = '\ue045'
FAST_FORWARD_BUTTON = '\ue00f'
FEDORA = '\ue041'
FIREBALL = '\ue04f'
FLAG_ALGERIA = '\ue054'
FLAG_ARGENTINA = '\ue05f'
FLAG_AUSTRALIA = '\ue058'
FLAG_BRAZIL = '\ue035'
FLAG_CANADA = '\ue039'
FLAG_CHILE = '\ue061'
FLAG_CHINA = '\ue037'
FLAG_CZECH_REPUBLIC = '\ue057'
FLAG_EGYPT = '\ue052'
FLAG_FRANCE = '\ue03c'
FLAG_GERMANY = '\ue034'
FLAG_INDIA = '\ue03a'
FLAG_INDONESIA = '\ue03d'
FLAG_IRAN = '\ue05d'
FLAG_ITALY = '\ue03e'
FLAG_JAPAN = '\ue03b'
FLAG_KUWAIT = '\ue053'
FLAG_MALAYSIA = '\ue056'
FLAG_MEXICO = '\ue033'
FLAG_NETHERLANDS = '\ue040'
FLAG_PHILIPPINES = '\ue060'
FLAG_POLAND = '\ue05e'
FLAG_QATAR = '\ue051'
FLAG_RUSSIA = '\ue036'
FLAG_SAUDI_ARABIA = '\ue055'
FLAG_SINGAPORE = '\ue059'
FLAG_SOUTH_KOREA = '\ue03f'
FLAG_UNITED_ARAB_EMIRATES = '\ue050'
FLAG_UNITED_KINGDOM = '\ue038'
FLAG_UNITED_STATES = '\ue032'
HAL = '\ue042'
HEART = '\ue047'
HELMET = '\ue049'
LEFT_ARROW = '\ue001'
LEFT_BUTTON = '\ue005'
LOCAL_ACCOUNT = '\ue030'
LOGO_FLAT = '\ue00c'
MIKIROG = '\ue062'
MOON = '\ue04d'
MUSHROOM = '\ue04a'
NINJA_STAR = '\ue04b'
OUYA_BUTTON_A = '\ue01c'
OUYA_BUTTON_O = '\ue019'
OUYA_BUTTON_U = '\ue01a'
OUYA_BUTTON_Y = '\ue01b'
PALM_TREE = '\ue066'
PARTY_ICON = '\ue027'
PAUSE_BUTTON = '\ue016'
PLAY_BUTTON = '\ue015'
PLAY_PAUSE_BUTTON = '\ue00e'
PLAY_STATION_CIRCLE_BUTTON = '\ue012'
PLAY_STATION_CROSS_BUTTON = '\ue011'
PLAY_STATION_SQUARE_BUTTON = '\ue014'
PLAY_STATION_TRIANGLE_BUTTON = '\ue013'
POTATO = '\ue065'
REWIND_BUTTON = '\ue00d'
RIGHT_ARROW = '\ue002'
RIGHT_BUTTON = '\ue007'
SANTA_HAT = '\ue064'
SHIFT = '\ue00a'
SKULL = '\ue046'
SPIDER = '\ue04e'
TEST_ACCOUNT = '\ue028'
TICKET = '\ue01f'
TICKET_BACKING = '\ue029'
TOKEN = '\ue01d'
TOP_BUTTON = '\ue006'
TROPHY0A = '\ue02d'
TROPHY0B = '\ue02e'
TROPHY1 = '\ue02a'
TROPHY2 = '\ue02b'
TROPHY3 = '\ue02c'
TROPHY4 = '\ue02f'
UP_ARROW = '\ue003'
VIKING_HELMET = '\ue04c'
YIN_YANG = '\ue044'

bacommon.transfer module

Functionality related to transferring files/data.

Warning

This is an internal api and subject to change at any time. Do not use it in mod code.