bacommon package¶
Functionality and data shared by all Ballistica components.
This includes clients, various servers, tools, etc.
Subpackages¶
Submodules¶
bacommon.analytics module¶
Analytics support.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
bacommon.app module¶
Common high level values/functionality related to Ballistica apps.
- class bacommon.app.AppArchitecture(*values)[source]¶
Bases:
EnumProcessor 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:
EnumA 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:
EnumOverall 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:
EnumA unique Ballistica build variation within a single platform.
Each distinct permutation of an app has a unique combination of
AppPlatformandAppVariant. 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'¶
- SERVER = 'server'¶
Server bundles we distribute for third-party operators (such as those on the ballistica.net downloads page).
- SERVER_BASN = 'server_basn'¶
Our own cloud-hosted game servers.
- 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:
EnumProcess 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.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
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.
bacommon.assetpackage module¶
Client-facing types related to asset packages.
- class bacommon.assetpackage.ApverNum¶
An asset-package-version’s numeric id: a permanent int minted by the master server when the version is created, never reused, and a full alternate to the version’s string id (
a-0.babuiltinassets.260927). Numeric ids are what machinery stores and sends; string ids are for people (display, authoring). A distinct type (a plaininton the wire) so one can’t be mixed up with other ints, such as asset indices.alias of
int
- class bacommon.assetpackage.AssetPackageDisplayInfo(name: str, owner: str)[source]¶
Bases:
objectNames an asset package for people (as in an error message).
Sent alongside resolve failures that are about one particular package (
AUTH_REQUIRED,ACCESS_DENIED) so the client can say which package it is in its own (translated) words, e.g. “asset package ‘foo’ by efro”.
- class bacommon.assetpackage.AssetPackageResolveError(*values)[source]¶
Bases:
EnumWhy an asset-package resolve failed (structured for branching).
The server sends this back when a resolve fails, so the client can react precisely (e.g. prompt for sign-in on
AUTH_REQUIRED) rather than parsing the human-readable error string. On the client it arrives asbabase.AssetResolveError.code.New values may be added over time; code branching on these should handle unrecognized ones (older clients see any value they don’t know as
INTERNAL).- ACCESS_DENIED = 'access'¶
Caller is authenticated but lacks access to this (non-public) version (not the owner / not on the package’s dev team).
- AUTH_REQUIRED = 'auth'¶
Caller is unauthenticated and the version is non-public; signing in with an account that has access may resolve it.
- CLIENT_TOO_OLD = 'tooold'¶
The client build is too old to address current asset-package manifests (which use clean source-named logical paths); the user must update. Clients predating the build-number field also land here.
- CONTENT = 'content'¶
The package’s own source content failed to build – a problem the package author can fix (e.g. a malformed sound or texture file). The human-readable error names the offending source file(s); clients should surface it verbatim. Old clients see this as
INTERNAL.
- INTERNAL = 'internal'¶
An internal/assemble error occurred server-side.
- INVALID = 'invalid'¶
A requested dimension value was invalid (texture profile/quality, language, etc.).
- NOT_FOUND = 'notfound'¶
The requested asset-package-version id is unknown / invalid.
bacommon.automationchannel module¶
Payload types for the automation SmartSocket channel kind.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
One root pair per channel kind (see the hierarchy-per-contract rule
in streamcall-smartsocket.md): a driver sends
AutomationCommand up, the device answers with
AutomationEvent. The relay never decodes either – a new
payload type must never require a basn rollout.
The channel is a sibling of the cloud console’s, deliberately: both carry code to a device and results back. What differs is who may – a console session is authorized by account ownership and ships in every build, while this one is authorized by a device-minted key (so a signed-out test device can be driven) and exists only in developer builds. That difference is why the capability is separate rather than an option on the console’s.
Client-visible, so these definitions are a public wire contract once a build speaking them ships: storage names and type-id values may never be repurposed, and removed ones stay retired. Add rather than change.
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.characterbuilder module¶
Character builders: the authoring form of a character.
A .bchar file in an assets workspace holds one
CharacterBuilder as json. It says everything needed for a
character to appear in the store and in games – its name, its icon,
its default colors, and how it looks and is proportioned in play –
by pointing at other assets in the same workspace.
This is an authoring format: written by people (or the workspace editor), stored once, and read by the server, which builds the compact definitions clients actually receive from it. So its keys are plain words, and nothing here is sent to game clients as is.
Every asset is named by its logical path: its source file’s path
from the workspace root, minus the extension (zoe/head.obj is
zoe/head). Logical paths are unique across all kinds of asset in
a workspace, so a path alone is never ambiguous. Assets in other
workspaces or packages cannot be referenced; copy what a character
needs into its own workspace.
Files inside published package versions are kept forever, so this
schema only ever grows: new optional fields, or a new
CharacterBuilderData kind beside the existing ones.
- class bacommon.characterbuilder.BasicSpazBuilder(name: str, icon_texture: str, icon_mask_texture: str, color: tuple[float, float, float], highlight: tuple[float, float, float], color_texture: str, color_mask_texture: str, head_mesh: str, torso_mesh: str, upper_arm_mesh: str, upper_leg_mesh: str, lower_leg_mesh: str, toes_mesh: str, forearm_mesh: str | None = None, hand_mesh: str | None = None, pelvis_mesh: str | None = None, left_upper_arm_mesh: str | None = None, left_forearm_mesh: str | None = None, left_hand_mesh: str | None = None, left_upper_leg_mesh: str | None = None, left_lower_leg_mesh: str | None = None, left_toes_mesh: str | None = None, highlight_name: str = '', highlight_editing: bool = True, highlight2: tuple[float, float, float] = (1.0, 1.0, 1.0), highlight2_name: str = '', highlight2_editing: bool = False, jump_sounds: list[str] = <factory>, attack_sounds: list[str] = <factory>, impact_sounds: list[str] = <factory>, death_sounds: list[str] = <factory>, pickup_sounds: list[str] = <factory>, fall_sounds: list[str] = <factory>, torso_radius: float = 0.15, shoulder_offset: tuple[float, float, float] = (0.0, 0.0, 0.0), thigh_radius: float = 0.04, ankle_radius: float = 0.07, step_separation: float = 0.08, idle_arm_stiffness: float = 1.0, arm_swing: float = 0.6, idle_sway: float = 0.05, eye_style_left: ~bacommon.characterbuilder.BuilderEyeStyle = BuilderEyeStyle.REGULAR, eye_style_right: ~bacommon.characterbuilder.BuilderEyeStyle = BuilderEyeStyle.REGULAR, eye_scale: float = 1.0, eye_offset: tuple[float, float, float] = (0.065, -0.036, 0.205), eye_color: tuple[float, float, float] = (0.5, 0.5, 1.2), eyeball_color: tuple[float, float, float] = (0.46, 0.38, 0.36), eyelid_color: tuple[float, float, float] = (0.5, 0.3, 0.2), eyelid_angle: float = 0.0, reflection_scale: float = 0.1, flippers: bool = False, attachments: list[~bacommon.characterbuilder.BuilderAttachment] = <factory>)[source]¶
Bases:
CharacterBuilderDataA classic spaz: per-body-part meshes sharing one texture.
Has one name, one icon, a color and up to two highlights. Every part is drawn from a single color texture, tinted through a single mask texture: the mask’s red channel takes the character’s color, its green channel the highlight and its blue channel the second highlight. Arm and leg meshes are authored for the character’s right side and mirrored for the left; a
left_version of any of them gives the left side a mesh of its own.Numbers left out take the standard spaz’s values, so a builder need only state what makes its character different.
- attachments: list[BuilderAttachment]¶
Extra pieces hung off the character’s body parts, such as hair, antennas and hats.
- color: tuple[float, float, float]¶
The character’s own colors. It is shown in these wherever nobody has picked others (the store, a new profile).
- eye_offset: tuple[float, float, float] = (0.065, -0.036, 0.205)¶
Where one eye sits relative to the head’s center; the other mirrors it.
- eye_style_left: BuilderEyeStyle = 'regular'¶
- eye_style_right: BuilderEyeStyle = 'regular'¶
- flippers: bool = False¶
For a character whose arms are flippers or fins, meaning one long upper-arm mesh that is the whole limb. Its arms are drawn shorter while it wears boxing gloves, so they don’t poke through the gloves. This changes only that; to leave out the forearms and hands such a character doesn’t have, leave
forearm_meshandhand_meshunset.
- forearm_mesh: str | None = None¶
Optional. Left out, nothing is drawn for that part (an upper-arm mesh may be the whole arm; a big round body may hide its pelvis).
- classmethod get_type_id() CharacterBuilderDataTypeID[source]¶
Return the type-id for this subclass.
- highlight2: tuple[float, float, float] = (1.0, 1.0, 1.0)¶
A second highlight, taken by the masks’ blue channel. White (the default) leaves what that channel covers untinted, so a character that doesn’t use one needn’t say anything.
- highlight2_name: str = ''¶
As
highlight_nameandhighlight_editing, for the second highlight (plain name: ‘Highlight 2’). Not editable unless a character says so: most don’t have one.
- highlight_editing: bool = True¶
Whether someone using the character may pick its highlight themselves. Off for one whose highlight is part of who it is.
- highlight_name: str = ''¶
What the highlight is called where someone picks it (a profile editor): the logical path of a string in the same workspace, as
nameis, for a character whose highlight is something in particular (‘Hair Color’). Empty: plain ‘Highlight’.
- icon_texture: str¶
The picture that stands for the character in menus, and its tint mask (red takes the color, green the highlight).
- idle_arm_stiffness: float = 1.0¶
How stiffly the lower arms are held while idle. 1 is fully rigid; lower values give relaxed dangling arms.
- left_upper_arm_mesh: str | None = None¶
Optional left-side versions of the arm and leg meshes, for a character whose two sides differ (one clawed hand, a peg leg). Each replaces the mesh of the same name for the character’s left side only; left out, the left side draws that mesh as usual. A left mesh is still authored as a right-side part and drawn mirrored, exactly as the shared one is. (A side can’t be given nothing this way: leaving one out means ‘same as the right’.)
- name: str¶
The character’s name, as the logical path of a string (a
.bstrfile) in the same workspace, so it can be translated.
- class bacommon.characterbuilder.BuilderAttachSide(*values)[source]¶
Bases:
EnumWhich of a paired body part’s two sides an attachment is on.
Like the limb meshes themselves, a limb (or wing) attachment is authored for the character’s right side and drawn mirrored on the left.
- BOTH = 'both'¶
Both sides. The only choice for the head, torso and pelvis.
- LEFT = 'left'¶
- RIGHT = 'right'¶
- class bacommon.characterbuilder.BuilderAttachTarget(*values)[source]¶
Bases:
EnumA body part an attachment can hang off.
- FOREARM = 'forearm'¶
- HAND = 'hand'¶
- HEAD = 'head'¶
- LOWER_LEG = 'lower_leg'¶
- PELVIS = 'pelvis'¶
- TOES = 'toes'¶
- TORSO = 'torso'¶
- UPPER_ARM = 'upper_arm'¶
- UPPER_LEG = 'upper_leg'¶
- WING = 'wing'¶
Not a body part but a place wings go; a pair of spots at the character’s back that swing and flap as wings do. Whatever is attached here is the wing, and a character has wings exactly when something is. Its forward (+z) points out along the wing from the middle of the torso. Sided like a limb, and like a limb it takes static attachments only.
- class bacommon.characterbuilder.BuilderAttachment(target: ~bacommon.characterbuilder.BuilderAttachTarget, kind: ~bacommon.characterbuilder.BuilderAttachmentKind, segments: list[~bacommon.characterbuilder.BuilderAttachmentSegment], side: ~bacommon.characterbuilder.BuilderAttachSide = BuilderAttachSide.BOTH, transform: ~bacommon.characterbuilder.BuilderTransform = <factory>, stiffness: float = 0.5, damping: float = 0.5, drag: float = 0.5, curl: float = 0.0, length: float = 0.5, radius: float = 0.0, curl_change: float = 0.0, length_change: float = 0.0, radius_change: float = 0.0, stiffness_change: float = 0.0, damping_change: float = 0.0)[source]¶
Bases:
objectOne piece hung off one of a character’s body parts.
The tuning numbers below run 0 to 1 (or -1 to 1 where noted) and what they adjust depends on the kind; a static attachment ignores them all.
- curl: float = 0.0¶
Resting bend at every joint of a chain, -1 to 1. Positive bends toward the attachment’s own up; 1 is a half turn per joint.
- curl_change: float = 0.0¶
How much the same-named number above changes from one segment of a chain to the next, -1 to 1 each. A chain can run stiff at the root and floppy at the tip, say.
- kind: BuilderAttachmentKind¶
- length: float = 0.5¶
Segment length. The default 0.5 is the kind’s own length; 0 is half of it and 1 is one and a half times it. Art is unaffected.
- radius: float = 0.0¶
Segment thickness. The default 0 is the kind’s own; 1 is three times it. Art is unaffected.
- segments: list[BuilderAttachmentSegment]¶
Art for each of the kind’s segments, root first.
- side: BuilderAttachSide = 'both'¶
- target: BuilderAttachTarget¶
The body part it hangs off.
- transform: BuilderTransform¶
Where it is anchored on the body part and which way it rests. A static attachment is simply drawn there. Scale is not used; resize a segment’s own transform instead.
- class bacommon.characterbuilder.BuilderAttachmentKind(*values)[source]¶
Bases:
EnumHow an attachment moves.
Each kind is a ready-made physical rig; an attachment picks one, places it, tunes it and supplies art for its segments. All of them are for looks only and never affect play.
The
LEGACY_kinds reproduce one classic hair rig and twirl oddly when disturbed; prefer theANTENNAkinds for anything new.- ANTENNA = 'antenna'¶
A stiff single capsule that mostly holds its pose, for antennas, ears, horns and the like. One segment.
- ANTENNA_2 = 'antenna_2'¶
A chain of two such capsules. Two segments.
- ANTENNA_3 = 'antenna_3'¶
A chain of three. Three segments.
- ANTENNA_4 = 'antenna_4'¶
A chain of four. Four segments.
- LEGACY_PONYTAIL_2 = 'legacy_ponytail_2'¶
A stiffer root capsule plus a floppy tip. Two segments.
- LEGACY_TUFT_LARGE = 'legacy_tuft_large'¶
Single softly-jointed capsule. One segment.
- LEGACY_TUFT_MEDIUM = 'legacy_tuft_medium'¶
The same with a medium-sized capsule. One segment.
- LEGACY_TUFT_SMALL = 'legacy_tuft_small'¶
The same with a small capsule. One segment.
- STATIC = 'static'¶
No movement at all, just art fixed to the body part, for hats, horns, badges and the like. One segment. The only kind limbs and wings can take.
- class bacommon.characterbuilder.BuilderAttachmentSegment(mesh: str, texture: str | None = None, mask_texture: str | None = None, transform: BuilderTransform = <factory>)[source]¶
Bases:
objectThe art for one segment of an attachment.
Drawn with the character’s own texture, tint mask, color and highlight, like every other piece of it, unless it names textures of its own.
- mask_texture: str | None = None¶
A tint mask of its own, read as the character’s
color_mask_textureis. Left out, that one is used. For art with a texture of its own that should take no tint at all, name an all-black mask here.
- texture: str | None = None¶
A color texture of its own, by logical path. Left out, the character’s
color_textureis used, which suits art whose picture is part of the character’s one texture.
- transform: BuilderTransform¶
How the mesh is drawn on its segment (for a static attachment, on the body part itself).
- class bacommon.characterbuilder.BuilderEyeStyle(*values)[source]¶
Bases:
EnumHow one of a character’s eyes is drawn.
- LIDLESS = 'lidless'¶
Bare eyeball; the lid appears only during blinks.
- NONE = 'none'¶
No eye drawn at all (painted-on or eyeless heads, eyepatches).
- REGULAR = 'regular'¶
Eyeball plus a resting/blinking eyelid.
- class bacommon.characterbuilder.BuilderTransform(translate: tuple[float, float, float] = (0.0, 0.0, 0.0), rotate: tuple[float, float, float] = (0.0, 0.0, 0.0), scale: tuple[float, float, float] = (1.0, 1.0, 1.0))[source]¶
Bases:
objectA placement: move, turn and resize, in that order of effect.
Applied to a thing in the space of whatever it is attached to. The thing is resized about its own origin, then turned, then moved; so
scalenever changes how fartranslatemoves it, androtatenever swings it around some distant point. Left out entirely, a transform changes nothing.rotateis heading, pitch and roll in degrees, applied in that order and described the way an aircraft’s are, the thing’s ‘nose’ being its own forward (+z) axis:heading turns it about the up axis of what it is attached to. Positive turns right, so something pointing out of a character’s nose swings toward the character’s right ear.
pitch then raises its nose. Positive aims it up, so toward the top of a head.
roll then spins it about its own nose without changing where that points. Positive is clockwise seen from behind.
- bacommon.characterbuilder.CHARACTER_BUILDER_EXTENSION = '.bchar'¶
The file extension of a character builder in a workspace.
- class bacommon.characterbuilder.CharacterBuilder(data: CharacterBuilderData)[source]¶
Bases:
objectOne character; a
.bcharfile’s contents.Deliberately just a wrapper: everything that defines the character – even its name, icon and colors – belongs to its
CharacterBuilderDatakind, so a later kind is free to define any of those differently (several highlights, none, …). Fields land here only if they turn out to be common to every kind.- data: CharacterBuilderData¶
The character itself.
- class bacommon.characterbuilder.CharacterBuilderData[source]¶
Bases:
IOMultiType[CharacterBuilderDataTypeID]A complete character of some kind; see the kinds below.
A kind owns everything about its characters: name, icon, colors and in-game form alike.
- classmethod get_type(type_id: CharacterBuilderDataTypeID) type[CharacterBuilderData][source]¶
Return a specific subclass given a type-id.
Should be overridden by child classes. Generally, users of the class should call
get_type_cached()instead of this, as it is more efficient.
- classmethod get_type_id() CharacterBuilderDataTypeID[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).
bacommon.characterranges module¶
The allowed span of each numeric value in a character.
The one table of these spans. The game clamps every value it reads to its span here (its constants are generated from this module), the cloud checks delivered characters against it, and authoring tools show and build characters clamped to it, so what an author sees is what plays.
Physique spans are exactly what the legacy style presets covered. They are simulation state (every machine in a game derives the same rigid bodies from them), so widening one changes gameplay. The look spans are looser than legacy to give characters visual headroom.
Changing a span here changes it everywhere at once, the next time each side is built. An older game build keeps the spans it shipped with, so a widened span only takes effect on builds made after the change.
- bacommon.characterranges.ATTACHMENT_RANGES: dict[str, Range] = {'curl': (-1.0, 1.0), 'curl_change': (-1.0, 1.0), 'damping': (0.0, 1.0), 'damping_change': (-1.0, 1.0), 'drag': (0.0, 1.0), 'length': (0.0, 1.0), 'length_change': (-1.0, 1.0), 'position': (-0.5, 0.5), 'radius': (0.0, 1.0), 'radius_change': (-1.0, 1.0), 'stiffness': (0.0, 1.0), 'stiffness_change': (-1.0, 1.0)}¶
Spans for an attachment’s values.
positionis where a simulated attachment sits on its body part (the classic hair rig peaks at 0.3); the rest are its tuning dials, whose whole useful span this is, so none can be overdriven.
- bacommon.characterranges.BASIC_SPAZ_RANGES: dict[str, Range] = {'ankle_radius': (0.045, 0.07), 'arm_swing': (0.3, 0.6), 'color': (0.0, 1.0), 'eye_color': (0.0, 2.0), 'eye_offset': (-0.5, 0.5), 'eye_scale': (0.1, 2.0), 'eyeball_color': (0.0, 1.0), 'eyelid_angle': (-30.0, 30.0), 'eyelid_color': (0.0, 1.0), 'highlight': (0.0, 1.0), 'idle_arm_stiffness': (0.2, 1.0), 'idle_sway': (0.02, 0.05), 'reflection_scale': (0.0, 2.0), 'shoulder_offset': (-0.05, 0.03), 'step_separation': (0.03, 0.08), 'team_coloring_strength': (0.0, 1.0), 'thigh_radius': (0.04, 0.06), 'torso_radius': (0.11, 0.3)}¶
Spans for a basic spaz’s own values, keyed by what the value is.
highlightcovers every highlight color andteam_coloring_strengthevery team-coloring strength.
bacommon.characterrig module¶
The fixed numbers of a character’s rig.
How a character is put together physically: the bodies it is simulated as, where its joints anchor on them, and how its art is placed on them. These are the game’s own values. The game’s code is generated from this module, so anything else that reads it (tools that show a character outside the game, say) is looking at exactly what plays.
Nearly everything here is simulation state: every machine in a game builds the same bodies from it, so changing a number changes gameplay for every character at once. Treat edits accordingly.
- bacommon.characterrig.ATTACHMENT_RIGS: dict[str, tuple[AttachmentSegmentRig, ...]] = {'antenna': (AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True),), 'antenna_2': (AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True), AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.065), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True)), 'antenna_3': (AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True), AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.065), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True), AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.065), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True)), 'antenna_4': (AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True), AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.065), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True), AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.065), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True), AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.065), child_anchor=(0.0, 0.0, -0.065), linear_stiffness=0.8, linear_damping=0.0005, angular_stiffness=1e-05, angular_damping=1e-08, collides=True)), 'legacy_ponytail_2': (AttachmentSegmentRig(geom_radius=0.09, geom_length=0.1, mass_radius=0.0, mass_length=0.0, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, -0.01, 0.1), linear_stiffness=1.0, linear_damping=0.03, angular_stiffness=0.0015, angular_damping=3e-06, collides=True), AttachmentSegmentRig(geom_radius=0.09, geom_length=0.13, mass_radius=0.0, mass_length=0.0, density=0.01, parent_anchor=(0.0, 0.01, -0.1), child_anchor=(0.0, -0.01, 0.12), linear_stiffness=0.4, linear_damping=0.02, angular_stiffness=0.00025, angular_damping=1e-06, collides=False)), 'legacy_tuft_large': (AttachmentSegmentRig(geom_radius=0.07, geom_length=0.13, mass_radius=0.0, mass_length=0.0, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, -0.08, -0.12), linear_stiffness=0.2, linear_damping=0.01, angular_stiffness=0.00025, angular_damping=1e-06, collides=True),), 'legacy_tuft_medium': (AttachmentSegmentRig(geom_radius=0.055, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, -0.08, -0.12), linear_stiffness=0.2, linear_damping=0.01, angular_stiffness=0.00025, angular_damping=1e-06, collides=True),), 'legacy_tuft_small': (AttachmentSegmentRig(geom_radius=0.04, geom_length=0.13, mass_radius=0.07, mass_length=0.13, density=0.01, parent_anchor=(0.0, 0.0, 0.0), child_anchor=(0.0, -0.08, -0.12), linear_stiffness=0.2, linear_damping=0.01, angular_stiffness=0.00025, angular_damping=1e-06, collides=True),)}¶
The segments of each kind of simulated attachment, first segment first. Keyed by kind, in the game’s own order of kinds, which must not change (a static attachment has no rig and is not here).
- class bacommon.characterrig.AttachmentSegmentRig(geom_radius: float, geom_length: float, mass_radius: float, mass_length: float, density: float, parent_anchor: Vec3, child_anchor: Vec3, linear_stiffness: float, linear_damping: float, angular_stiffness: float, angular_damping: float, collides: bool = True)[source]¶
Bases:
objectThe rig of one simulated segment of an attachment.
A segment is a capsule lying along its own z axis, hung by a spring joint from the segment before it (or, for the first, from the spot on the body part where its attachment sits).
- bacommon.characterrig.SPAZ_RIG = SpazRig(head_radius=0.23, pelvis_size=(0.35, 0.16, 0.1), roller_radius=0.3, upper_arm_radius=0.06, upper_arm_length=0.16, lower_arm_radius=0.06, lower_arm_length=0.13, upper_leg_length=0.12, lower_leg_span=0.26, toes_radius=0.075, stand_head_height=2.25, stand_torso_height=1.8, stand_pelvis_height=1.66, stand_roller_height=1.6, pelvis_joint_drop=0.05, pelvis_joint_forward=0.05, shoulder_on_torso=(-0.15, 0.14, 0.0), shoulder_on_upper_arm=(0.02, 0.0, -0.1), elbow_on_upper_arm=(0.0, 0.0, 0.07), elbow_on_lower_arm=(0.0, 0.0, -0.08), hip_on_pelvis=(-0.1, -0.01, 0.0), hip_on_upper_leg=(0.0, 0.0, -0.05), knee_on_upper_leg=(0.0, 0.0, 0.05), knee_on_lower_leg=(0.0, 0.0, -0.05), ankle_on_lower_leg=(0.0, 0.05, 0.05), ankle_on_toes=(0.0, -0.04, 0.0), forearm_stretch_pivot=0.1, hand_offset=0.04, hand_turn=10.0, eye_mesh_scale=0.09, eyeball_pitch=-10.0)¶
The spaz rig’s numbers.
- class bacommon.characterrig.SpazRig(head_radius: float = 0.23, pelvis_size: Vec3 = (0.35, 0.16, 0.1), roller_radius: float = 0.3, upper_arm_radius: float = 0.06, upper_arm_length: float = 0.16, lower_arm_radius: float = 0.06, lower_arm_length: float = 0.13, upper_leg_length: float = 0.12, lower_leg_span: float = 0.26, toes_radius: float = 0.075, stand_head_height: float = 2.25, stand_torso_height: float = 1.8, stand_pelvis_height: float = 1.66, stand_roller_height: float = 1.6, pelvis_joint_drop: float = 0.05, pelvis_joint_forward: float = 0.05, shoulder_on_torso: Vec3 = (-0.15, 0.14, 0.0), shoulder_on_upper_arm: Vec3 = (0.02, 0.0, -0.1), elbow_on_upper_arm: Vec3 = (0.0, 0.0, 0.07), elbow_on_lower_arm: Vec3 = (0.0, 0.0, -0.08), hip_on_pelvis: Vec3 = (-0.1, -0.01, 0.0), hip_on_upper_leg: Vec3 = (0.0, 0.0, -0.05), knee_on_upper_leg: Vec3 = (0.0, 0.0, 0.05), knee_on_lower_leg: Vec3 = (0.0, 0.0, -0.05), ankle_on_lower_leg: Vec3 = (0.0, 0.05, 0.05), ankle_on_toes: Vec3 = (0.0, -0.04, 0.0), forearm_stretch_pivot: float = 0.1, hand_offset: float = 0.04, hand_turn: float = 10.0, eye_mesh_scale: float = 0.09, eyeball_pitch: float = -10.0)[source]¶
Bases:
objectThe fixed numbers of a spaz’s own rig (see
SPAZ_RIG).Lengths are in the game’s world units and angles in degrees. A capsule lies along its own z axis and its length leaves out its two round caps. Anything given for one side is the character’s right; the left mirrors it in x.
- eyeball_pitch: float = -10.0¶
An eyeball’s resting turn about its x axis, so it looks slightly down.
- forearm_stretch_pivot: float = 0.1¶
The point this far along a forearm is the one its art shrinks about when flippers draw at half length (under boxing gloves).
- lower_leg_span: float = 0.26¶
A lower leg’s capsule is this long less twice its radius (the character’s ankle radius), so its whole span stays the same whatever its thickness.
- pelvis_joint_drop: float = 0.05¶
The joint between pelvis and torso sits this far below the torso’s center.
- pelvis_joint_forward: float = 0.05¶
On the torso, that joint also sits this far forward, like the curve of a spine.
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.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
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.consolechannel module¶
Payload types for the cloud-console SmartSocket channel kind.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
One root pair per channel kind (see the hierarchy-per-contract rule
in streamcall-smartsocket.md): commands go up from the console as
ConsoleCommand, everything it displays comes back down as
ConsoleEvent. The relay never decodes either – a new event
type must never require a basn rollout.
Payloads here are pure data. In particular they carry log entries and levels, not pre-colored html; rendering belongs to whoever displays them. That is both the payload-model rule and one less hand-rolled escaping site than the polling console this replaced.
Client-visible, because the app itself can hold the far end of a console session. That makes these definitions a public wire contract once a build that speaks them ships: storage names and type-id values may never be repurposed, and removed ones stay retired. Add rather than change.
bacommon.depiction module¶
Depictions: self-sufficient things for clients to draw.
A depiction describes something to show – a character’s icon, a
name, a character in 3D, an image – that a client draws natively
wherever it is hosted: a doc-ui page (see
bacommon.docui.v2.Depiction), a page’s viewer pane, and later
widgets and in-game nodes. The host decides where and how big; the
depiction decides what is drawn there.
Depictions are self-sufficient: each carries whatever package references it needs, and never adds packages to whatever contains it. Art that isn’t local yet shows as a standin and upgrades in place when it arrives; the container never waits on it.
A client that doesn’t recognize a depiction’s kind draws a placeholder (an outlined box with a question mark) and warns once, since producers should only send kinds a client understands. Any finer-grained degradation – a name drawing plain text when its styling is too new for the client, say – is each kind’s own business.
- class bacommon.depiction.BlankDepiction(color_override: tuple[float, float, float] | None = None, team_coloring: bool = False)[source]¶
Bases:
DepictionNothing at all: an empty doc-ui viewer pane, say.
Whatever its host draws around it (a viewer pane’s background and frame) is what shows. A doc-ui page wanting an empty pane beside its rows shows one of these, since a page with no viewer at all gets no pane (see
bacommon.docui.v2.Page.viewer). Clients beforebacommon.docui.v2.PAGE_DRIVEN_VIEWER_BUILDdon’t know it (seebacommon.docui.v2.blank_viewer()).- color_override: tuple[float, float, float] | None = None¶
See
Depiction. (Ignored; there’s nothing to color.)
- classmethod get_type_id() DepictionTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.depiction.CharacterIconDepiction(icon: str, color_override: tuple[float, float, float] | None = None, team_coloring: bool = False)[source]¶
Bases:
DepictionA character’s icon, drawn square.
iconis an opaque, cloud-composed icon definition (a character’s icon component on its own; the schema is server-private, see bamasterbaserver/character.py), tinted by its own colors. A standard-character standin shows while the art isn’t local.A color override replaces the icon’s main color, the way a team’s color replaces a character’s own on its spaz; highlights stay the icon’s own.
- classmethod get_type_id() DepictionTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.depiction.CharacterViewerDepiction(spaz: str, camera_shake: float = 1.0, camera_gyro: float = 1.0, color_override: tuple[float, float, float] | None = None, team_coloring: bool = False)[source]¶
Bases:
DepictionA character standing in a little scene of its own, live in 3D.
spazis an opaque, cloud-composed spaz definition (a character’s in-game form on its own; seeCharacterIconDepiction); it alone decides how the character looks, colors included. A host keeps the viewer running across updates, so a changed definition (an editor’s draft, say) updates the picture in place rather than starting it over.- camera_gyro: float = 1.0¶
How far turning the device (on devices with a gyro) swings the camera around the character, relative to the usual: 2.0 swings twice as far, 0 (or less) not at all, and 10 is the most there is (more counts as 10). (Not at all regardless while the viewer has camera gyro disabled.)
- camera_shake: float = 1.0¶
How much the camera’s hand-held wobble moves, relative to the usual: 2.0 wobbles twice as much, 0 (or less) holds it still, and 10 is the most there is (more counts as 10). (Still regardless while the viewer has camera shake disabled.)
- color_override: tuple[float, float, float] | None = None¶
See
Depiction. (Viewers ignore it; the definition alone decides the character’s colors.)
- classmethod get_type_id() DepictionTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.depiction.Depiction[source]¶
Bases:
IOMultiType[DepictionTypeID]Something for a client to draw; see the module docs.
Every kind carries an optional
color_override: one rgb color imposed on the depiction, baked in. It is the static form of the color override a host can impose live (a display node’scolor_override, say), and a host’s wins where both are set. Each kind decides what it applies to (a name’s text and whatever its capsule routes it to, a character icon’s main color); kinds with nothing to apply it to ignore it. A player’s icon in a teams game carries its team’s color this way, so it shows in that color wherever it is drawn.Every kind likewise carries
team_coloring: the depiction’s main color is a team’s and should stay its clearly dominant one. Each kind decides what that means, toning its other colors down (a character icon’s highlights; the parts of a name its override isn’t routed to); kinds with nothing to tone ignore it. The main color is the color override if there is one, else whatever the kind takes as its own. Hosts can also turn it on live (a display node’steam_coloring).(Both are declared on each kind rather than here, as this base holds no fields; they are
coandtcoon the wire for all.)- classmethod get_type(type_id: DepictionTypeID) type[Depiction][source]¶
Return a specific subclass given a type-id.
Should be overridden by child classes. Generally, users of the class should call
get_type_cached()instead of this, as it is more efficient.
- classmethod get_type_id() DepictionTypeID[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.depiction.DepictionTypeID(*values)[source]¶
Bases:
EnumType ID for each of our subclasses.
- BLANK = 'bl'¶
- CHARACTER_ICON = 'ci'¶
- CHARACTER_VIEWER = 'cv'¶
- IMAGE = 'i'¶
- MESH_VIEWER = 'mv'¶
- NAME = 'n'¶
- UNKNOWN = 'u'¶
- class bacommon.depiction.ImageDepiction(texture: TextureSpec | int, aspect: float = 1.0, scale: float = 1.0, color: tuple[float, float, float, float] | None = None, mask_texture: TextureSpec | int | None = None, tint_texture: TextureSpec | int | None = None, tint_color: tuple[float, float, float] | None = None, tint2_color: tuple[float, float, float] | None = None, tint3_color: tuple[float, float, float] | None = None, packages: list[ApverNum] | None = None, domain_digest: str | None = None, color_override: tuple[float, float, float] | None = None, team_coloring: bool = False)[source]¶
Bases:
DepictionA texture, fetched on demand, optionally masked and tinted.
For art that must be self-sufficient – anything whose package the page (or other container) should not have to resolve first – such as the main toolbar’s chests. Page art that resolves with its page is better served by the
bacommon.docui.v2.Imagedecoration.Texture slots take a full spec or, in the compact wire form, an integer index into the index domain laid out over
packages(as character components do); producers send the compact form, withdomain_digestguarding the indices.- aspect: float = 1.0¶
Width over height. Stated here rather than taken from the loaded texture so the standin shown until the art arrives has the same shape, and nothing shifts when it does.
- color_override: tuple[float, float, float] | None = None¶
See
Depiction. (Images ignore it for now.)
- domain_digest: str | None = None¶
Producer’s (prefix) digest of that domain, checked by the client before resolving indices (see
bacommon.assetspec.wire_digest()).
- classmethod get_type_id() DepictionTypeID[source]¶
Return the type-id for this subclass.
- mask_texture: TextureSpec | int | None = None¶
- packages: list[ApverNum] | None = None¶
Packages laid out (in order) as the index domain integer texture slots index into; None when every slot is a full spec. (A plain None default rather than a list factory: a factory default breaks Sphinx’s signature rendering of this class.)
- scale: float = 1.0¶
Draws the image this much bigger or smaller about its box’s center, for art with glows or other extras that should spill past where it is placed. Only the drawing changes: the box (and so layout and what counts as a press) stays put. Clients clamp this to 0-2; ones before this field draw at 1.
- texture: TextureSpec | int¶
- tint3_color: tuple[float, float, float] | None = None¶
Tint through the tint texture’s blue channel (as
tint_coloris red andtint2_colorgreen). Clients before this field ignore it.
- tint_texture: TextureSpec | int | None = None¶
- class bacommon.depiction.MeshViewerCamera[source]¶
Bases:
IOMultiType[MeshViewerCameraTypeID]A
MeshViewerDepiction’s camera, placed by hand.Either held in one place (
MeshViewerStillCamera) or moving (MeshViewerPingPongCamera). A client that doesn’t know a kind frames things automatically, as with no camera at all.- classmethod get_default_type_id() MeshViewerCameraTypeID[source]¶
Return a type-id to be assumed when none is present.
By default, dataclassio errors when deserializing multitype data that contains no type-id value. Overriding this to return a type-id changes that behavior: data with no type-id present will be deserialized as the returned type, and instances of that type will be serialized without a type-id value. This both saves a bit of space and allows ‘upgrading’ an existing regular dataclass to a multitype - simply designate the original dataclass type as the default and old serialized data will remain loadable (and data for the default type will remain loadable by old code).
Be aware of the following, however:
Once serialized data exists anywhere without type-id values, the default type-id must never be changed or removed; doing so would cause that existing data to be silently reinterpreted as some other type (or to error).
A missing type-id normally acts as a sanity check when deserializing; defining a default effectively disables that check, meaning malformed data may deserialize successfully as the default type instead of erroring.
- classmethod get_type(type_id: MeshViewerCameraTypeID) type[MeshViewerCamera][source]¶
Return a specific subclass given a type-id.
Should be overridden by child classes. Generally, users of the class should call
get_type_cached()instead of this, as it is more efficient.
- classmethod get_type_id() MeshViewerCameraTypeID[source]¶
Return the type-id for this subclass.
- classmethod get_unknown_type_fallback() MeshViewerCamera[source]¶
Return a fallback object in cases of unrecognized types.
This can allow newer data to remain readable in older environments. Use caution with this option, however, as it effectively modifies data.
- class bacommon.depiction.MeshViewerCameraPose(position: tuple[float, float, float], target: tuple[float, float, float], fov_y: float)[source]¶
Bases:
objectWhere a
MeshViewerCamerais at some moment.In the depiction’s own space: the same one its entries’
positionvalues place them in.
- class bacommon.depiction.MeshViewerCameraTypeID(*values)[source]¶
Bases:
EnumType ID for each kind of
MeshViewerCamera.- PING_PONG = 'pp'¶
- STILL = 's'¶
- UNKNOWN = 'u'¶
- class bacommon.depiction.MeshViewerDepiction(entries: list[MeshViewerEntry], spin: bool = True, draggable: bool = True, bounds_scale: float = 1.0, view_yaw: float = 0.0, view_pitch: float | None = None, camera: MeshViewerCamera | None = None, camera_shake: float | None = None, camera_gyro: float = 1.0, background_color: tuple[float, float, float] | None = None, packages: list[ApverNum] | None = None, domain_digest: str | None = None, color_override: tuple[float, float, float] | None = None, team_coloring: bool = False)[source]¶
Bases:
DepictionA few meshes, live in 3D and slowly turning.
Its
entriesare placed relative to one another; the client frames them all to fill the picture (by the bounds meshes carry), turns them slowly, and lets them be swung around by dragging, much as the asset browser’s mesh preview does. On devices with a gyro, turning the device swings the view around what the camera looks at (seecamera_gyro). A host keeps the viewer running across updates, so changed entries reframe in place.- background_color: tuple[float, float, float] | None = None¶
What shows behind the meshes; None for the host’s usual viewer background (a doc-ui viewer pane’s own blank color, say).
- bounds_scale: float = 1.0¶
How big an area it frames, relative to the bounds of everything in it (about the same center): 2.0 frames twice as much, so its meshes look half as big. Meant for shrinking (1 and up); below 1 crops in (and well below, the camera ends up in among them).
- camera: MeshViewerCamera | None = None¶
A camera placed by hand, for a set shot (held still or moving; see
MeshViewerCamera). When given, nothing is framed automatically (bounds_scaleandview_pitchdon’t apply) and dragging up and down does nothing. The turntable still turns everything about the origin (withspin,view_yaw, and side to side drags).
- camera_gyro: float = 1.0¶
How far turning the device (on devices with a gyro) swings the camera around what it looks at, relative to the usual: 2.0 swings twice as far, 0 (or less) not at all, and 10 is the most there is (more counts as 10). The usual is a generous swing for framed views and a subtler one (a third as far) under a
cameraplaced by hand. (Not at all regardless while the viewer has camera gyro disabled.)
- camera_shake: float | None = None¶
How much the camera’s hand-held wobble moves, as
CharacterViewerDepiction.camera_shakedoes: 1.0 is the character viewer’s usual, 0 (or less) holds it still, and 10 is the most there is. None for the default: 1.0 with acameraplaced by hand (a set shot), else 0.
- color_override: tuple[float, float, float] | None = None¶
See
Depiction. (Ignored; entries carry their own colors.)
- domain_digest: str | None = None¶
Producer’s (prefix) digest of that domain (see
ImageDepiction).
- draggable: bool = True¶
Whether dragging on it swings it around. Off, with
spinoff too, it’s a completely fixed view.
- entries: list[MeshViewerEntry]¶
- classmethod get_type_id() DepictionTypeID[source]¶
Return the type-id for this subclass.
- packages: list[ApverNum] | None = None¶
Packages laid out (in order) as the index domain integer slots index into; None when every slot is a full spec (see
ImageDepiction).
- spin: bool = True¶
Whether it turns on its own. (When it doesn’t, it always shows exactly its
view_yawto start with.)
- class bacommon.depiction.MeshViewerEntry(mesh: MeshSpec | int, texture: TextureSpec | int | None = None, color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0), transparent: bool = False, reflection: CubeMapTextureSpec | int | None = None, reflection_scale: tuple[float, float, float] = (1.0, 1.0, 1.0), tint_texture: TextureSpec | int | None = None, tint_color: tuple[float, float, float] = (1.0, 1.0, 1.0), tint2_color: tuple[float, float, float] = (1.0, 1.0, 1.0), tint3_color: tuple[float, float, float] = (1.0, 1.0, 1.0), position: tuple[float, float, float] = (0.0, 0.0, 0.0), rotation: tuple[float, float, float] = (0.0, 0.0, 0.0), scale: tuple[float, float, float] = (1.0, 1.0, 1.0))[source]¶
Bases:
objectOne mesh shown by a
MeshViewerDepiction, and where.- color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0)¶
Multiplied into its texture (or its whole color, untextured): rgba. Alpha only shows when
transparent.
- reflection: CubeMapTextureSpec | int | None = None¶
A cube map it reflects (a full spec or an index); None for none.
- reflection_scale: tuple[float, float, float] = (1.0, 1.0, 1.0)¶
Multiplied into what it reflects (rgb).
- rotation: tuple[float, float, float] = (0.0, 0.0, 0.0)¶
How it’s turned, in degrees about x, then y, then z.
- texture: TextureSpec | int | None = None¶
Its texture, likewise; with none it draws in
coloralone.
- tint2_color: tuple[float, float, float] = (1.0, 1.0, 1.0)¶
Tint through the tint texture’s green channel.
- tint3_color: tuple[float, float, float] = (1.0, 1.0, 1.0)¶
Tint through the tint texture’s blue channel.
- tint_color: tuple[float, float, float] = (1.0, 1.0, 1.0)¶
Tint through the tint texture’s red channel.
- tint_texture: TextureSpec | int | None = None¶
A texture whose red, green and blue channels say where
tint_color,tint2_colorandtint3_colorapply (a character’s color mask, say); None for no tinting.
- class bacommon.depiction.MeshViewerPingPongCamera(a: MeshViewerCameraPose, b: MeshViewerCameraPose, travel_time: float, loop: bool = True)[source]¶
Bases:
MeshViewerCameraA camera moving back and forth between two poses.
It starts at
a, sways over tob, and sways back, over and over: one smooth sine wave, fastest midway and easing into each turnaround (position, target and field of view all move together, in straight lines). Time only passes while the viewer is shown, and an update leaving the camera as it was carries on without starting over.- a: MeshViewerCameraPose¶
Where it starts.
- b: MeshViewerCameraPose¶
Where it goes.
- classmethod get_type_id() MeshViewerCameraTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.depiction.MeshViewerStillCamera(position: tuple[float, float, float], target: tuple[float, float, float], fov_y: float)[source]¶
Bases:
MeshViewerCameraA camera held in one place.
In the depiction’s own space (see
MeshViewerCameraPose, whose fields these are).- classmethod get_type_id() MeshViewerCameraTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.depiction.MeshViewerUnknownCamera[source]¶
Bases:
MeshViewerCameraA camera of a kind this client doesn’t know.
Treated as no camera at all: things are framed automatically.
- classmethod get_type_id() MeshViewerCameraTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.depiction.NameDepiction(name: str, color_override: tuple[float, float, float] | None = None, team_coloring: bool = False)[source]¶
Bases:
DepictionA name: an in-game name, an account name, a character’s name.
nameis an opaque, cloud-composed name definition (the same form a character’s name component takes; the schema is server-private, see bamasterbaserver/character.py). Its basic form is colored text; richer forms (a capsule with an icon, as account names are shown) fall back to it on clients that don’t understand them. Its shape is its measured text (or capsule), fitted into the box by the alignment.- classmethod get_type_id() DepictionTypeID[source]¶
Return the type-id for this subclass.
bacommon.displayitem module¶
The producer-side display-item: what to show, and in what style.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
A display-item is a request – show this thing in this style, in these
bounds – which is what it has always been. It turns into plain doc-ui
decorations (DisplayItem.decorations), so the client needs no
knowledge of item types to draw one: new types need no client update.
The same decorations draw into a doc-ui page or, via the doc-ui prep
code, into any plain container widget.
The depiction lives here rather than in either host so there is exactly
one of it, serving both the client and the master server. What each
host must supply is gathered into DepictionAssets.
bacommon.hostui module¶
Wire types for host-defined ui (host doc-ui and party notices).
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
A game host can give connected clients buttons in their in-game menu
and party window, serve the doc-ui pages those buttons open, and post
a party notice. Everything here rides the per-connection json message
channel between host and client (BA_JMESSAGE_HOST_UI), never the
scene stream: it is per client, not per activity, and never recorded
into replays. Design: docs/initiatives/host-docui.md.
These shapes go between game builds, so once a build speaking them ships, storage names and type-id values may never be repurposed. Add rather than change.
bacommon.legacydisplayitem 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.
- bacommon.locale.LANGUAGE_PICKER_SEPARATOR = '·'¶
Separator between a language picker entry’s two halves.
This is a workaround for an engine text-layout quirk, and the exact character matters. Do not “clean this up” to an em dash.
The game client absorbs punctuation into an adjacent OS-rendered text span on purpose, so punctuation next to OS-drawn glyphs doesn’t visually mismatch it (
TextGraphics::IsOSDrawableAscii, whose comment says as much). The layout loop is roughly:if (IsOSDrawableAscii(val) && !os_span.empty()) { os_span.push_back(val); // absorbed } else if (Glyph* g = GetGlyph(val, big)) { ... // baked glyph; CLOSES the span } else { os_span.push_back(val); // no glyph -> absorbed anyway }An RTL endonym opens an OS span. A separator that gets absorbed joins that span, so bidi runs over “<arabic> - “ as one unit, resolves the trailing neutrals against an RTL paragraph, and draws them on the left:
- العربية Arabic. A separator that instead closes the span leaves the Arabic alone in it, where it shapes correctly.U+00B7 MIDDLE DOT closes it because it clears both gates, and each is independently required:
It is above every ASCII range
IsOSDrawableAsciitests (32-47, 58-64, 91-96, 123-126), so it is not absorbed. An ASCII hyphen (45) sits in the first range and IS absorbed – which is why swapping the em dash for a hyphen changed nothing.It is under
kGlyphCount(1280) andg_glyph_map[183]is a real font page, so it has a baked glyph and takes the span-closing branch.
Miss either and it breaks: an em dash (U+2014) clears gate 1 but is above 1280, so it has no baked glyph, falls to the final
else, and is absorbed regardless. Same for a bullet (U+2022). So the rule is NOT “use any non-ASCII character” – it is “128-1279 with a baked glyph”, or one of the ASCII charactersIsOSDrawableAsciiexplicitly excludes (parens, quotes, brackets).Parentheses would work too and were the original form, but they nest badly: the qualified variants already carry their own parenthetical on both sides, giving
Português (Brasil) (Portuguese (Brazil)).Verified on Android 2026-07-30. That generalizes across platforms in a way the earlier bidi-isolate attempt did not: this hinges on the baked font sheet and on platform-independent engine code, not on each platform’s text stack. Full trace, including two failed approaches, in ballistica-internal
docs/followups.md.
- class bacommon.locale.Locale(*values)[source]¶
Bases:
EnumA 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.
- 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:
EnumA resolved
Localefor use in logic.These values should never be stored or transmitted and should always come from resolving a
Localewhich 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'¶
- property decimal_mark: str[source]¶
The character separating a number’s whole and fraction parts.
Locale data, not translated content: a decimal mark is a fact about a language, and asking a translation model for one invites a confidently wrong answer that no reviewer would catch by reading the string. So it is curated here and applied by formatting code rather than authored into any asset package.
Deliberately covers only the mark. Digit grouping is not modelled: our formatted numbers scale their units (
1.2 GB, never1234.5 MB), so a group separator has nothing to separate, and skipping it avoids the messier per-locale rules (1,234,567/1 234 567/12,34,567). Digit shaping is likewise out: locales that could use non-ASCII digits read Western ones fine.Arabic and Persian are
'.'rather than'٫'on purpose – that mark pairs with Eastern Arabic digits, which we do not emit.
- property duration_separator: str[source]¶
The joiner between composed duration parts (“1h 23m”).
Locale data, not translated content, same as
decimal_mark: most languages join abbreviated duration parts with a space, while CJK compounds join with none (“1時間45分”). Curated here and applied by formatting code so it can never be a confidently wrong translation, and so a new locale is a type error rather than a silent space.
- property endonym: str[source]¶
This locale’s name written in this locale’s own language.
Static per-locale data, deliberately not a translated string: a language picker shows every option at once, which no single-locale string table can do (a client holds one locale’s strings at a time). It is also what lets a user find their language whatever the ui is currently set to. Pair it with a translated language name – see the
strings/localesgroup in BaCommonAssets – to render e.g. “Svenska (Swedish)”.Values match what shipped in the legacy
lang_names_translatedblob, so players see no churn, with these deliberate deviations:English was simply absent there and is supplied here.
Slovak’s stray trailing space is dropped.
Latin-American Spanish gains the accent it was missing (“Latinoamérica”).
Gibberish’s was ‘Abuktarika’, which sorted it to the very top of every picker – a surprising spot for a joke locale. It now starts with a G so it lands where someone hunting for it would look, keeping the mock-English style of the locale’s own output (‘Germaunish’, ‘Sveendishsprok’).
Persian’s wrapping bidi controls (U+2066/U+200E) are dropped. Directional isolation is a rendering concern, and both consumers already give each entry its own container (an html
<option>, a popup-menu row), so the run cannot bleed into a neighbor. A caller placing one of these inline in mixed text should isolate it there.
- property endonym_sort_key: str[source]¶
Sort key for listing locales by
endonym.Endonym order is what a language picker wants: it is the half of the label the eye scans for, and unlike ordering by translated name it does not reshuffle the whole list when the ui language changes.
Accents are folded so a leading diacritic doesn’t exile an entry from its own alphabet – raw codepoint order files “Čeština” after “Wikang Pilipino”. Non-Latin scripts still land after Latin ones, which reads as a reasonable grouping rather than an error. Deliberately not full locale-aware collation: that needs ICU, and the result here is stable and good enough for a few dozen entries.
Lives here rather than in each picker because there are two of them – the client’s settings window and the master server’s account-settings page – and they silently disagreed about ordering until 2026-07-30.
- 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’). A tag naming a language we have no locale for falls back to English (with a warning, so we can add it); a malformed tag raises
ValueError.Use
from_tag_or_none()when a non-match is an ordinary outcome rather than something to complain about – negotiating a browser’sAccept-Languagelist, say, where most entries are expected to miss.
- static from_tag_or_none(tag: str) LocaleResolved | None[source]¶
Return a locale for a string tag, or None if we have none.
The quiet counterpart to
from_tag(): returnsNoneinstead of warning and falling back to English, and swallows the malformed-tagValueError. This is what locale negotiation wants – walking a client’s preference list, a miss is the normal case and must be distinguishable from a genuine match on English.Note that an unknown region within a known language is still a match (
zh-XXresolves to simplified Chinese), and still warns – that one is worth adding.
- 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.
- bacommon.locale.language_picker_label(locale: LocaleResolved, translated_name: str) str[source]¶
Build one entry for a language picker.
Reads
"<endonym> · <name in the reader's language>", e.g."Svenska · Swedish", collapsing to just the endonym when the two would be identical (the entry for the language you already use). The endonym lets a speaker find their own language whatever the ui is set to; the translated name lets everyone else identify a script they cannot read. Shape follows macOS’s language list, which uses a dash; seeLANGUAGE_PICKER_SEPARATORfor why we cannot.Known remaining wart, engine-side: the space before the separator still gets absorbed into an RTL endonym’s span, so those rows render slightly tight on that side (
العربية· Arabic). And when both halves are non-ASCII – an RTL endonym in a CJK ui – everything lands in one span whatever the separator, and one of the two orders always reverses. That one needs real mixed-direction layout in the engine (docs/followups.md). The master server’s html picker is unaffected throughout: the browser lays out the whole string itself.Lives beside the data rather than in either picker because there are two of them – the client’s settings window and the master server’s account-settings page – and they have already drifted once on ordering.
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
strhas its{name}placeholders replaced bystr(args['name']).A
StringSelectorpicks 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:
ExceptionA malformed localized value or a missing/bad argument.
- class bacommon.loctext.PluralCategory(*values)[source]¶
Bases:
EnumA CLDR plural category.
- FEW = 'few'¶
- MANY = 'many'¶
- ONE = 'one'¶
- OTHER = 'other'¶
- TWO = 'two'¶
- ZERO = 'zero'¶
- class bacommon.loctext.SelectorKind(*values)[source]¶
Bases:
EnumHow a
StringSelectorchooses 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:
objectA localized string whose final form is chosen at render time.
The structured alternative to a plain
strvalue:formsmaps a form key to its leaf text (which may carry{name}substitutions and, for plurals,#for the count). ForPLURALthe keys are CLDR category names (one/few/…/other) or=Nexact-count matches; forSELECTthey are the possible string values ofarg.otheris the fallback. TheIOAttrskeys are the on-the-wire schema shared with the nativeLstr2port.- kind: SelectorKind¶
- bacommon.loctext.evaluate(value: str | StringSelector, locale: Locale, /, **args: object) str[source]¶
Resolve a localized
valueto a final string.valueandlocaleare positional-only so they cannot collide with a substitution namedvalueorlocale– without that, an authored string using{value}is unrenderable through this function, which is a trap that surfaces only once someone writes such a string.valueis a plainstr({name}substitutions only) or aStringSelector(plural/select choice, then substitution).argssupplies the named arguments; plural selection useslocale’s CLDR rule (seeplural_category()). RaisesLocTextErroron a missing/ill-typed argument or a selector with no matching form and noother.
- bacommon.loctext.plural_category(locale: Locale, n: int) PluralCategory[source]¶
Return the CLDR cardinal plural category for an integer in a locale.
nis treated as a non-negative integer count (its absolute value is used). Uses the locale’s resolved form so obsolete aliases (e.g. the bareSPANISH) follow their modern locale’s rule. Every shipped locale is mapped explicitly; a genuinely-unknown locale falls back to theone-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
pluralmessage must cover for a locale.The set the locale’s rule can produce over the integers, plus the mandatory
otherfallback (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 rulesplural_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.loctext.substitution_names(value: str | StringSelector) set[str][source]¶
Return the substitution-argument names a value consumes.
For a plain string these are its
{name}tokens; for aStringSelectorits pivotargplus any{name}tokens in its forms. This is the single source consumers use to derive a string’s parameter set from its value (e.g. rebuilding positional-substitution order client-side) – the producer-side round-trip validation guarantees every parameter’s token survives into every stored output, so the derivation is total.
bacommon.loggercontrol module¶
System for managing loggers.
- class bacommon.loggercontrol.LoggerControlConfig(levels: dict[str, int]=<factory>)[source]¶
Bases:
objectA 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.
- get_effective_level(logname: str) int[source]¶
Given a log name, predict its level if this config is applied.
bacommon.logging module¶
Logging functionality.
- class bacommon.logging.ClientLoggerName(*values)[source]¶
Bases:
EnumLogger 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'¶
- LOG_REPORTING = 'ba.logreport'¶
- NETWORKING = 'ba.net'¶
- PERFORMANCE = 'ba.perf'¶
- UI = 'ba.ui'¶
- USER = 'ba.user'¶
- V2TRANSPORT = 'ba.v2transport'¶
- WORKSPACE = 'ba.workspace'¶
- 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.
bacommon.logreporting module¶
Shared bits for triggered client log reporting.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
The server hands a client a LogReportSpec (via transient
cloud-vals) describing when to trip a log report and how much
surrounding context to ship. The client then ships the resulting
entry range incrementally, tracking its progress with a
LogReportWindow.
Everything here is pure logic shared by the client reporter, the server’s ingest path, and tests; nothing engine-specific belongs in this module.
bacommon.meshzstddict module¶
Zstandard dictionaries for compressing display-mesh (.bob) data.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
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.metascan module¶
Scanner for # ba_meta directives in Python source.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
Recognized directive shapes:
# ba_meta require api <N>— module-level API version requirement. Whenexpected_api_versionis supplied, modules whose value doesn’t match are skipped (and listed inScanResults.incorrect_api_modules). When it isNonethe 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 <NUM>— module declares that it needs the asset-package-version with that numeric id 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.
bacommon.nativeenums module¶
Enums shared with the native layer.
The engine’s C++ versions of these are generated from this module, so
nothing but enum may be imported here, and every class must be
an Enum with ascending int values. Values are spelled
out because some are stored or sent places; never renumber existing
ones. These are also available as babase.InputType and so on.
- class bacommon.nativeenums.InputType(*values)[source]¶
Bases:
EnumTypes of input a controller can send to the game.
- BOMB_PRESS = 8¶
- BOMB_RELEASE = 9¶
- DOWN_PRESS = 25¶
- DOWN_RELEASE = 26¶
- FLY_PRESS = 13¶
- FLY_RELEASE = 14¶
- HOLD_POSITION_PRESS = 17¶
- HOLD_POSITION_RELEASE = 18¶
- JUMP_PRESS = 4¶
- JUMP_RELEASE = 5¶
- LEFT_PRESS = 19¶
- LEFT_RELEASE = 20¶
- LEFT_RIGHT = 3¶
- PICK_UP_PRESS = 10¶
- PICK_UP_RELEASE = 11¶
- PUNCH_PRESS = 6¶
- PUNCH_RELEASE = 7¶
- RIGHT_PRESS = 21¶
- RIGHT_RELEASE = 22¶
- RUN = 12¶
- START_PRESS = 15¶
- START_RELEASE = 16¶
- UP_DOWN = 2¶
- UP_PRESS = 23¶
- UP_RELEASE = 24¶
- class bacommon.nativeenums.Permission(*values)[source]¶
Bases:
EnumPermissions that can be requested from the OS.
- STORAGE = 0¶
- class bacommon.nativeenums.QuitType(*values)[source]¶
Bases:
EnumTypes of quit behavior that can be requested from the app.
- ‘soft’ may hide/reset the app but keep the process running, depending
on the platform (generally a thing on mobile).
- ‘back’ is a variant of ‘soft’ which may give ‘back-button-pressed’
behavior depending on the platform. (returning to some previous activity instead of dumping to the home screen, etc.)
- ‘hard’ leads to the process exiting. This generally should be avoided
on platforms such as mobile where apps are expected to keep running until killed by the OS.
- BACK = 1¶
- HARD = 2¶
- SOFT = 0¶
- class bacommon.nativeenums.SoundPriority(*values)[source]¶
Bases:
EnumHow much a sound matters when more sounds want to play than can at once. When everything is busy, a new sound takes the place of the oldest playing sound of the lowest priority at or below its own (sounds that loop are never cut off this way).
Sounds are ‘default’ unless their asset says otherwise.
- DEFAULT = 2¶
- HIGH = 3¶
- HIGHER = 4¶
- LOW = 1¶
- LOWER = 0¶
- class bacommon.nativeenums.UIScale(*values)[source]¶
Bases:
EnumThe overall scale the UI is being rendered for. Note that this is independent of pixel resolution. For example, a phone and a desktop PC might render the game at similar pixel resolutions but the size they display content at will vary significantly.
- ‘large’ is used for devices such as desktop PCs where fine details can
be clearly seen. UI elements are generally smaller on the screen and more content can be seen at once.
- ‘medium’ is used for devices such as tablets, TVs, or VR headsets.
This mode strikes a balance between clean readability and amount of content visible.
- ‘small’ is used primarily for phones or other small devices where
content needs to be presented as large and clear in order to remain readable from an average distance.
- LARGE = 2¶
- MEDIUM = 1¶
- SMALL = 0¶
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.packetzstddict module¶
Zstandard dictionaries for compressing scene game packets.
Warning
This is an internal api and subject to change at any time. Do not use it in mod code.
The game compresses each outgoing scene packet on its own (they average
under a hundred bytes), which zstd only does well with a dictionary
trained on what packets look like. The dictionary here is trained on
captures of hosted games (test_game_run --packet-dump on a host and
a client across several maps, mini-games and player counts; recipe in
docs/design/packet-compression.md) and is handed to the C++ layer
at app start.
Both ends of a connection must hold the identical dictionary, so
dictionaries are immutable and versioned: never mutate an existing
.zstddict file – add a new version instead – and bump the scene
protocol version when the game switches to it (the protocol version is
what tells a peer which dictionary is in play).
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.
bacommon.servermanager module¶
Functionality related to the server manager script.
- class bacommon.servermanager.ChatMessageCommand(message: str, clients: list[int] | None)[source]¶
Bases:
ServerCommandChat message from the server.
- class bacommon.servermanager.ClientListCommand[source]¶
Bases:
ServerCommandPrint a list of clients.
- class bacommon.servermanager.KickCommand(client_id: int, ban_time: int | None)[source]¶
Bases:
ServerCommandKick a client.
- class bacommon.servermanager.ScreenMessageCommand(message: str, color: tuple[float, float, float] | None, clients: list[int] | None)[source]¶
Bases:
ServerCommandScreen-message from the server.
- class bacommon.servermanager.ServerCommand[source]¶
Bases:
objectBase class for commands that can be sent to the server.
- class bacommon.servermanager.ServerConfig(party_name: str = 'FFA', party_is_public: bool = True, password: str | None = None, authenticate_clients: bool = True, admins: list[str] = <factory>, enable_default_kick_voting: bool = True, allow_punch_grab: bool = False, legacy_spaz_physics: bool = False, 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:
objectConfiguration for the server manager app (<appname>_server).
- class bacommon.servermanager.ShutdownCommand(reason: ShutdownReason, immediate: bool)[source]¶
Bases:
ServerCommandTells the server to shut down.
- reason: ShutdownReason¶
- class bacommon.servermanager.ShutdownReason(*values)[source]¶
Bases:
EnumReason a server is shutting down.
- NONE = 'none'¶
- RESTARTING = 'restarting'¶
- class bacommon.servermanager.StartServerModeCommand(config: ServerConfig)[source]¶
Bases:
ServerCommandTells the app to switch into ‘server’ mode.
- config: ServerConfig¶
bacommon.strbrief module¶
Parse the tag vocabulary in an assets_v1 .bstr authoring brief.
A .bstr input is a freeform AI-translation brief (decision D10)
carrying a small, strict inline tag vocabulary (D11) – the only
machine-readable part. Three consumers depend on these tags
deterministically: wrapper codegen (the typed accessor signature),
generation (whether/how to produce plural forms), and validation (tag
round-trip, D12). This module extracts and validates them.
The grammar is one rule:
tag := target [ '|' spec ]
target := ident | '@' refpath
spec := ident [ '(' kwargs ')' ]
kwarg := ident '=' ( int | float | true | false | ident )
Each character has exactly one job. @ marks where content comes
from – a term reference is resolved and inlined at generation time
(D12) rather than passed at call time, and is the only tag that is not
a wrapper param. : separates namespaces inside a reference
(@apref:path). | applies a rendering spec, the same filter
operator template languages use, so new forms cost a registry entry
rather than a new sigil:
{name}– runtime text substitution; astrwrapper param.{count|plural}– runtime number substitution; anintwrapper param and the plural pivot (the string gains one form per CLDR category).{@myterms:strings/apple}– a term reference. Alwaysapref:path; colon-less same-package refs are not supported.
Braces escape as {{ and }}, exactly as they do at runtime
(bacommon.loctext). The two layers must agree on what a brace
means, or a brief and its own translated output disagree about which
spans are tags.
Nested braces are rejected on purpose, so a raw ICU message pasted into a brief fails loudly rather than parsing as garbage – the brief layer uses this grammar, not ICU.
This is the brief (authoring) layer. What gets stored and evaluated at
runtime is an ICU-MessageFormat-subset message
(bacommon.loctext); the producer expands these tags into that form
({count|plural} -> {count, plural, one {...} other {...}}).
Lives in shared bacommon (rather than the bamaster producer, its
original home) so authoring-side tooling can validate briefs before
upload – assetworkspace put parses every local .bstr brief and
refuses the sync on parse errors, surfacing mistakes (duplicate tags,
pasted ICU, bad names, unknown specs) at edit time instead of minutes
later inside a server translation run. The producer imports this same
module, so the grammar cannot drift between the two.
- class bacommon.strbrief.BriefSignature(tags: list[BriefTag])[source]¶
Bases:
objectThe full tag signature extracted from a brief (in order).
- property params: list[BriefTag]¶
Wrapper params (everything but term refs), in order.
First-appearance order. Term refs are excluded – they’re resolved at generation time, not passed at call time.
- property pivot: BriefTag | None¶
The single form-producing tag, or None.
The coexistence invariant guarantees at most one (enforced by
parse_brief()).
- class bacommon.strbrief.BriefSpec(name: str, args: dict[str, int | float | bool | str]=<factory>)[source]¶
Bases:
objectA parsed rendering spec – the
foo(a=1)in{x|foo(a=1)}.
- class bacommon.strbrief.BriefTag(kind: BriefTagKind, name: str, spec: BriefSpec | None = None)[source]¶
Bases:
objectOne parsed inline tag from a brief.
- property display_kind: str¶
The kind plus any spec args, in canonical encoded form.
What the language blob’s
kcarrier stores for a spec’d param: the bareparam_kindwhen the spec carries no args (keeping pre-args blobs byte-identical), elsekind(name=value,...)– the samename(args)shape spec bodies use, with args sorted by name and rendered canonically so identical briefs can never produce differing blob bytes. Decoded at render time byparse_display_kind().
- kind: BriefTagKind¶
- class bacommon.strbrief.BriefTagKind(*values)[source]¶
Bases:
EnumThe three things a tag can be.
- TERM = 'term'¶
A
{@path}term reference, inlined at generation time.
- TEXT = 'text'¶
A plain
{name}– text substituted at render time.
- VALUE = 'value'¶
A
{name|spec}– a typed value rendered by its spec.
- bacommon.strbrief.SPECS: dict[str, SpecDef] = {'data_size': SpecDef(param_kind='bytes', form_producing=False, args={'compact': <class 'bool'>}, arg_choices={}, validate_args=None, supported=True), 'duration': SpecDef(param_kind='millis', form_producing=False, args={'maxparts': <class 'int'>, 'decimals': <class 'int'>, 'dir': <class 'str'>, 'clamp': <class 'bool'>}, arg_choices={'dir': ('past', 'future')}, validate_args=<function _validate_duration_args>, supported=True), 'ordinal': SpecDef(param_kind='count', form_producing=True, args={}, arg_choices={}, validate_args=None, supported=False), 'plural': SpecDef(param_kind='count', form_producing=True, args={}, arg_choices={}, validate_args=None, supported=True)}¶
The spec registry – the whole extension point. A new rendering form is an entry here plus its handling in the producer/evaluator; the grammar above never changes.
ordinalis reserved: CLDR ordinal categories are a different form set from cardinals, so it is a pivot likeplural, not a formatter.
- class bacommon.strbrief.SpecDef(param_kind: str, form_producing: bool = False, args: dict[str, type] = <factory>, arg_choices: dict[str, tuple[str, ...]] = <factory>, validate_args: ~typing.Callable[[dict[str, int | float | bool | str]], str | None] | None = None, supported: bool = True)[source]¶
Bases:
objectWhat one registered rendering spec means.
param_kindis the coarse call-signature category consumed by wrapper codegen.form_producingmarks a spec that multiplies the string into per-plural-category forms rather than merely rendering a value into it – the property the one-pivot rule is checked against.argsmaps each accepted keyword to its type (anintliteral is accepted wherefloatis declared);arg_choicesoptionally restricts a string-typed arg to an enumerated value set, andvalidate_argsoptionally checks the whole parsed arg map (returning an error message or None) – both enforced at parse so a bad brief fails at author time, not at display.supportedis False for a name that is reserved but not implemented; it parses, then fails with a tailored message.
- bacommon.strbrief.parse_brief(text: str) BriefSignature[source]¶
Extract + validate the tag signature from a brief
input.Raises
CleanErroron raw ICU pasted into the brief, an empty/malformed tag, an invalid arg name, an unknown or unsupported spec, a bad spec argument, a duplicate arg name, or more than one form-producing spec (the coexistence invariant – a string with two independent counts should be split into two entries).
- bacommon.strbrief.parse_display_kind(expr: str) tuple[str, dict[str, int | float | bool | str]][source]¶
Split a
k-carrier kind expression into (kind, args).The inverse of
BriefTag.display_kind: a bare kind ('bytes') yields empty args;'bytes(compact=true)'yields the decoded arg map. Arg values are typed by literal shape (true/false-> bool, digits -> int/float, else identifier string) – the declared spec-arg types that governed authoring are not consulted here, since the display side dispatches on the kind and reads only the args it knows. RaisesCleanErroron a malformed expression; display-time callers surface that through their usual fail-visible path.
- bacommon.strbrief.parse_tag(body: str) BriefTag[source]¶
Parse and validate one tag body (the text between its braces).
The single-tag unit of
parse_brief(), for templates that use the same tag grammar outside a brief – the spec’d tokens ({t|duration}) a literal language-string template may carry. RaisesCleanErrorexactly asparse_brief()would for that tag.
- bacommon.strbrief.validate_round_trip(sig: BriefSignature, segments: Iterable[str], *, require_count: bool) None[source]¶
Assert a brief’s runtime tags survived correctly into generated text.
The post-generation half of the D12 contract:
every
{name}token param must appear verbatim in each segment (the model must not drop or rename a runtime substitution);no
{name}token other than those may appear (the model must not invent a substitution that would fail at render);with
require_count, each segment must contain the#count placeholder (every plural form renders the number);no term reference
{@...}may survive (those are resolved/inlined at generation time, never shipped as a token).
Escaped braces (
{{/}}) are literal text and are skipped throughout, matching the runtime evaluator.segmentsis the per-locale output text(s) to check: a single output string for a plain/text-sub entry, or the per-plural-form texts for a pivot entry (so each form is verified independently). RaisesCleanErroron any violation.
bacommon.text module¶
Text related bits.
- class bacommon.text.SpecialChar(*values)[source]¶
Bases:
EnumCustom unicode characters the engine can display.
Each value is the character itself, ready to drop into a string. These utilize the private-use block of unicode characters (U+E000-U+F8FF) and are specific to the game; exporting or rendering them elsewhere will be meaningless.
This is the one list of these characters; the engine’s own table is generated from it. It is also available as
babase.SpecialCharandbauiv1.SpecialChar.- ANNIVERSARY_15_BOMB = '\ue06a'¶
- ANNIVERSARY_15_CAKE = '\ue068'¶
- ANNIVERSARY_15_SPAZ = '\ue069'¶
- 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'¶
- DISCORD_LOGO = '\ue026'¶
- DOWN_ARROW = '\ue004'¶
- DPAD_CENTER_BUTTON = '\ue010'¶
- DRAGON = '\ue048'¶
- EXPLODINARY_LOGO = '\ue031'¶
- 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'¶
- GAME_CENTER_LOGO = '\ue021'¶
- GOOGLE_PLAY_GAMES_LOGO = '\ue020'¶
- HAL = '\ue042'¶
- HEART = '\ue047'¶
- HELMET = '\ue049'¶
- LEFT_ARROW = '\ue001'¶
- LEFT_BUTTON = '\ue005'¶
- LOCAL_ACCOUNT = '\ue030'¶
- LOGO = '\ue01e'¶
- LOGO_FLAT = '\ue00c'¶
- MIKIROG = '\ue062'¶
- MOON = '\ue04d'¶
- MUSHROOM = '\ue04a'¶
- NINJA_STAR = '\ue04b'¶
- NVIDIA_LOGO = '\ue05c'¶
- OCULUS_LOGO = '\ue05a'¶
- 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'¶
- POPUP_ICON = '\ue018'¶
- POTATO = '\ue065'¶
- REWIND_BUTTON = '\ue00d'¶
- RIGHT_ARROW = '\ue002'¶
- RIGHT_BUTTON = '\ue007'¶
- SANTA_HAT = '\ue064'¶
- SHIFT = '\ue00a'¶
- SKULL = '\ue046'¶
- SPIDER = '\ue04e'¶
- STEAM_LOGO = '\ue05b'¶
- 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'¶
- V2_LOGO = '\ue063'¶
- VIKING_HELMET = '\ue04c'¶
- YIN_YANG = '\ue044'¶
- property min_engine_build: int[source]¶
The first engine build able to draw this character.
Older builds have no glyph for it and show a blank, so anything offering characters to a particular client (a store, an icon picker) should leave out ones newer than that client’s build.
When adding a character, give it its own case here returning the build number that first ships its glyph. Everything that predates this property returns 0 (we don’t know when those were added, and every client we talk to can draw them).
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.