Animation trees (``.atr``) ============================ .. meta:: :description: Source-derived specification of the stock Call of Duty: United Offensive animation tree format: syntax, sync properties, pruning, runtime nodes, limits, and script integration. :keywords: call of duty, united offensive, cod uo, animtree, atr, animation tree, loopsync, nonloopsync, complete, xanim Status and authority -------------------- This page specifies the animation tree format loaded by stock Call of Duty: United Offensive. The authority is the recovered stock parser and runtime in ``coduomp``: * ``src/scripting/script_anim_runtime.c`` owns the parser and the tree registry; * ``src/animation/xanim_tree.c`` builds the compact runtime tree; * ``src/animation/xanim_tree_queries.c`` exposes queries over that tree; * ``src/scripting/script_compile_load.c`` and ``src/scripting/script_code_emit.c`` resolve ``%`` references and emit animation values; * ``src/client/cgame/animation/anim_tree_load.c`` and ``src/bg/bg_animation_tree_lookup.c`` load and look up trees. The format is a naming and grouping format. It does not store animation curves, frame data, or notetrack content. A leaf points at an ``xanim`` asset; the asset holds the animation itself. What a tree does ---------------- A tree gives scripts stable animation names. A script writes ``%run``, and the compiler resolves ``run`` in the tree selected by ``#using_animtree``. The reference becomes a packed animation value containing the tree registry index and the node index. A tree also groups animations: * a parent can group a body region, a weapon set, or a pose family; * a parent can declare that its children play in sync; * a parent can force its descendants to survive tree pruning. The tree is not a state machine. It has no transitions, conditions, or timers. The player animation script and the AI scripts add those layers. File location and selection --------------------------- The loader forms the path ``animtrees/.atr``. The name comes from a ``#using_animtree`` directive: .. code-block:: c #using_animtree("generic_human"); The name must be a single identifier. The stock parser accepts letters, digits, and underscores, and rejects a slash, dot, or hyphen in this position. The loader canonicalizes the name like a script filename, so case is not significant. Tree selection is compiler state. Each new source file starts without a selected tree. A ``#using_animtree`` directive selects a tree for the remainder of that file. A later directive in the same file can select another tree, and references after it resolve against the new tree. A ``%`` literal or an ``#animtree`` expression before any directive is a compile error. The engine also loads trees directly by name. For example, the player animation layer looks up the ``multiplayer`` tree, and a turret script can load a narrow tree such as ``mg42`` or ``30cal``. Syntax ------ The tree file contains a sequence of nodes. It has no outer pair of braces. .. code-block:: text tree = { node } ; node = name | name "{" { node } "}" | name ":" properties "{" { node } "}" ; properties = property { property } ; property = "loopsync" | "nonloopsync" | "complete" ; A leaf is a bare name: .. code-block:: text walk run strafe_left A parent is a name followed by a braced body: .. code-block:: text main { torso { stand_shoot stand_idle } legs { walk_loop run_loop } } A parent with properties uses a colon before the body: .. code-block:: text aim : nonloopsync { aim_left aim_center aim_right } The colon must be a separate token, and the property list runs to the end of the line. The opening and closing braces must each be followed only by the end of the line. These rules make the format line-oriented even though it uses braces. Names are identifiers. A duplicate name in one parent is a parse error: .. code-block:: text duplicate animation A name may appear under different parents because each parent has its own name table. The parser accepts that, but the tree resolver binds a name once per tree. If a script references a name that occurs more than once, the first node binds the reference and a later node reports ``duplicate animation '%s' in 'animtrees/%s.atr'`` during tree build. Keep names unique across a tree that scripts reference by name. Comments ~~~~~~~~ Both comment forms from the shared parser are accepted: .. code-block:: text // a line comment /* a block comment over several lines */ Stock files use both forms. ``generic_human.atr`` contains a block comment around a disabled sync group. Node kinds ---------- A leaf is an animation asset reference. The builder resolves it with ``FS_GetDataForFile("xanim", name, "")``. If the asset is missing, the load fails with: .. code-block:: text Cannot find 'xanim/%s' A parent is a group. It may contain leaves, other parents, or both. The runtime keeps a child count and a first-child index for each parent. The root of the compact tree is a synthetic node at index 0; the file's top-level nodes are its children. A parent is not an animation asset. It has no leaf asset, and ``XAnimGetLength`` is not valid for it. A sync parent still reports time through ``XAnimHasTime``. ``XAnimGetAnimName`` returns ```` for a parent that is asked for an animation name. The parser also accepts an alias form at the syntax level, but the stock implementation rejects it: .. code-block:: text FIXME: aliases not yet implemented Do not use aliases in stock content. Node properties --------------- .. list-table:: :header-rows: 1 :widths: 18 52 30 * - Property - Effect - Applies to * - ``loopsync`` - Marks the parent as a synchronous group whose leaves loop. The parent becomes a sync node and its children are played in phase. - Parent with a body. * - ``nonloopsync`` - Marks the parent as a synchronous group whose leaves do not loop. The parent becomes a sync node and its children are played in phase. - Parent with a body. * - ``complete`` - Keeps the parent's whole subtree when the parent is referenced by a script. It has no effect on playback. - Parent with a body. A property on a leaf is an error: .. code-block:: text properties cannot be applied to primitive animations Properties combine on one line: .. code-block:: text proneMG42_fire : complete loopsync { proneMG42_fire_15down { // five body poses } proneMG42_fire_level { // ... } proneMG42_fire_15up { // ... } } ``complete`` and the sync bits are separate flags. ``complete`` is bit 3, ``loopsync`` is bit 0, and ``nonloopsync`` is bit 1 in the recovered property encoding. Do not put both sync properties on one parent. The parser records both bits, and the sync setup rejects the node as both looping and non-looping. Sync groups ----------- A sync group makes several animations share a playback phase. The classic use is a set of aim or fire poses that differ by direction but must switch without drifting out of step. The sync rules are enforced when the compact tree is built: * every leaf under a ``loopsync`` parent must itself be a looping asset; * every leaf under a ``nonloopsync`` parent must be a non-looping asset; * a sync group may not contain another sync group; * a sync node may not be declared twice in the same subtree. A mismatch is fatal. The stock diagnostic is: .. code-block:: text animation cannot be sync looping and sync nonlooping A nested sync group is also fatal, reported as a duplicate sync specification. The practical rule is to keep looping and non-looping variants in separate parents. ``generic_human.atr`` contains a comment that a set of animations was moved into a separate group because a single sync group mixed the two. Pruning and placeholders ------------------------ The stock loader does not keep every node in the file. It keeps the nodes that scripts actually reference, plus the parts of the tree those references require. The sequence is: #. The script compiler records each ``%name`` reference against the selected tree. #. When the tree is loaded, the parser marks each node whose name is not in that reference set as ``missingFromTree``. #. The builder removes marked nodes unless an ancestor forces them to stay. #. ``complete`` on a referenced parent forces the whole subtree to stay. #. A referenced parent whose children were all removed receives a synthetic ``void`` child. A referenced ``loopsync`` parent receives ``void_loop``. #. An empty root tree also receives ``void``. This is why a sync group often carries ``complete``. A script may reference only one aim direction, but the sync group needs its siblings to keep phase. Without ``complete``, the unreferenced siblings can be pruned and the group loses the set it was built to synchronize. The behavior is controlled by ``com_animCheck``. The stock default is ``0``. Setting it to a nonzero value keeps every node, including nodes no script references. Use it to inspect a full tree or to expose a missing ``xanim`` asset that pruning would otherwise hide. Do not ship content that depends on the debugging value. Runtime representation ---------------------- The compact tree is an ``XAnim``: .. code-block:: c typedef struct { const char *name; uint32_t nodeCount; XAnimEntry entries[]; } XAnim; Each ``XAnimEntry`` is either a leaf or a parent: * a leaf has a resolved animation asset pointer; * a parent has a child count, a first-child index, and property flags; * every entry has a parent index. The root is index 0. The first child of the root is index 1. A parent's children occupy a contiguous range starting at ``firstChildIndex``. The builder walks the parsed tree, stamps parent indexes, resolves leaves, and then runs the sync setup over the result. The runtime queries are small: .. list-table:: :header-rows: 1 :widths: 32 68 * - Query - Result * - ``XAnimIsPrimitive`` - True for a leaf, false for a parent. * - ``XAnimHasTime`` - True for a leaf or for a parent with a sync property. * - ``XAnimIsLooped`` - True for a looping leaf asset or a looping sync parent. * - ``XAnimGetAnimName`` - The leaf asset name, or ````. * - ``XAnimGetLength`` - The leaf asset length in seconds. Valid only for a primitive node; the function reads ``leafAsset`` unconditionally, so a parent index is not a legal argument. * - ``XAnimGetNumChildren`` - The child count for a parent. The tree itself stores no frame data. Every leaf holds a reference to the loaded ``xanim`` asset. Script integration ------------------ A script selects a tree with a directive before its first animation reference: .. code-block:: c #using_animtree("generic_human"); It then writes animation literals with ``%``: .. code-block:: c self setAnimKnob(%stand_idle, 1, 0.2); The compiler resolves the name in the tree selected at that point in the file and emits a packed ``scr_anim_t`` value. The packing is: .. code-block:: text (treeIndex << 16) | nodeIndex The low 16 bits hold the node index. The high 16 bits hold the tree registry index. The registry index is nonzero; zero is reserved. This makes a reference to the root of the first registered tree pack as ``0x00010000``. ``#animtree`` emits the current tree's integer registry index. AI turret scripts use it to switch an entity to a narrow tree: .. code-block:: text self UseAnimTree(#animtree); The runtime keeps a table of loaded trees. ``Scr_FindAnimTree`` returns the loaded ``XAnim`` for a name, or ``NULL``. ``Scr_FindAnim`` records a reference to a name in a named tree. The runtime reports a missing name when it builds the tree. The stock diagnostics are: .. code-block:: text animation '%s' not defined in anim tree '%s' duplicate animation '%s' in 'animtrees/%s.atr' The first is a name that no node in the tree defines. ``Scr_CheckAnimsDefined`` raises it after ``Scr_CreateAnimationTree`` finishes. The second is a name that more than one node defines and a script references: the first node binds the reference, and a later node finds it already bound. Limits ------ .. list-table:: :header-rows: 1 :widths: 30 70 * - Limit - Value and consequence * - Tree name length - 49 bytes. The path buffer is ``MAX_QPATH`` (64 bytes), and the loader adds ``animtrees/`` and ``.atr``. A longer name fails with ``animation tree path is too long``. * - Tree registry - 127 registered names per active registration slot. The runtime keeps two slots. Overflow reports ``maximum animation tree count exceeded (%i)``. * - Node pool - ``XANIM_POOL_NODE_COUNT`` is 2048. This is an engine pool constant, not a per-file syntax rule. * - Node name - A node name must fit the shared identifier and path limits. A leaf name is also an ``xanim`` asset name. * - Property line - The property list must fit on one line. The parser does not continue a property list across lines. A tree is loaded once and reused by its registry index. Changing the file while the game is running does not update an already loaded tree. Diagnostics ----------- .. list-table:: :header-rows: 1 :widths: 16 44 40 * - Stage - Message - Meaning * - Parse - ``duplicate animation`` - The same name appears twice in one parent. * - Parse - ``properties cannot be applied to primitive animations`` - A leaf has a colon property list. * - Parse - ``FIXME: aliases not yet implemented`` - The file uses the unsupported alias form. * - Build - ``Cannot find 'xanim/%s'`` - A leaf names an asset that is not present. * - Build - ``animation '%s' in '%s' cannot be sync looping and nonlooping`` - A leaf under a ``loopsync`` parent is a non-looping asset. * - Build - ``animation '%s' in '%s' cannot be sync nonlooping and looping`` - A leaf under a ``nonloopsync`` parent is a looping asset. * - Build - ``animation cannot be sync looping and sync nonlooping`` - The parent declares both sync properties. * - Build - ``duplicate specification of animation sync in '%s', %d nodes above '%s'`` - A sync group is nested inside another sync group. The message names the tree, the depth of the inner node, and the leaf it reached. * - Build - ``animation '%s' not defined in anim tree '%s'`` - A ``%`` reference names a node the selected tree does not define. * - Build - ``duplicate animation '%s' in 'animtrees/%s.atr'`` - A ``%`` reference names a node that more than one node in the tree defines. * - Script compile - ``bad anim tree name`` - The ``#using_animtree`` argument is not an identifier. * - Script compile - ``#using_animtree was not specified`` - A ``%`` or ``#animtree`` appears before a tree directive. * - Script compile - ``cannot reference animation from /# ... #/ comment`` - A ``%`` reference appears inside a developer-only block. * - Load - ``animation tree path is too long`` - The tree name does not fit the ``animtrees/.atr`` path. * - Load - ``maximum animation tree count exceeded (%i)`` - The tree registry is full. Examples -------- Minimal root leaves ~~~~~~~~~~~~~~~~~~~ ``stalingrad_runner.atr`` is a single root-level leaf: .. code-block:: text stalingrad_flagrunner_idle This tree has no parent groups. A script can reference the leaf by name. Named groups ~~~~~~~~~~~~ ``bomber_anim.atr`` uses root-level groups for crew roles: .. code-block:: text pilot_anims { // ... } copilot_anims { // ... } wirelessop_anims { // ... } The group names are organizational. Scripts reference the leaves inside them, not the group path. Sync group ~~~~~~~~~~ ``trenches_truck_dummies.atr`` contains: .. code-block:: text walk_run : loopsync { // looping walk and run leaves } ``generic_human.atr`` contains both loop modes for the same pose family: .. code-block:: text stand_shoot : nonloopsync { // ... } stand_shoot_auto : loopsync { // ... } The two groups are separate because the leaves have different loop states. Complete sync group ~~~~~~~~~~~~~~~~~~~ ``multiplayer.atr`` contains: .. code-block:: text proneMG42_fire : complete loopsync { proneMG42_fire_15down { // five body poses } proneMG42_fire_level { // ... } proneMG42_fire_15up { // ... } } ``complete`` keeps the three fire pose groups when the parent is referenced. The sync property keeps their leaves in phase. Empty group ~~~~~~~~~~~ ``generic_human.atr`` contains an empty group: .. code-block:: text testmap_testing { } An empty group on its own is valid syntax. If a script references it, the loader inserts a ``void`` child so the parent is not empty. Empty groups that no script references are pruned. Authoring checklist ------------------- #. Confirm that every leaf has an ``xanim`` asset with the same logical name. #. Keep every script-referenced name unique across the tree. The parser only rejects duplicates within one parent. #. Put looping and non-looping variants in separate sync groups. #. Add ``complete`` to a sync group when scripts reference only some of its children. #. Reference the tree with a single identifier in ``#using_animtree``. #. Build with ``com_animCheck 1`` once to expose unreferenced missing assets. #. Return ``com_animCheck`` to ``0`` before shipping. #. Recheck the player animation table and AI scripts after renaming a node. A rename breaks every ``%`` reference that used the old name. Related pages ------------- The player animation table is specified in :doc:`player_animation_script`. The AI animation behavior scripts are specified in :doc:`ai_animation_scripts`. The compiler-side animation directives are summarized in :doc:`../gsc_uo/language`. Implementation basis -------------------- .. list-table:: :header-rows: 1 :widths: 52 48 * - Source - Role * - ``src/scripting/script_anim_runtime.c`` - Tree parser, property flags, registry, pruning, script reference collection. * - ``src/scripting/script_anim.h`` - Animation tree and reference types. * - ``src/animation/xanim_tree.c`` - Compact tree builder, leaf resolution, parent indexes, sync setup. * - ``src/animation/xanim_tree_queries.c`` - Primitive, time, loop, length, child, and name queries. * - ``src/qcommon/xanim_types.h`` - ``XAnim`` and ``XAnimEntry`` layout. * - ``src/qcommon/script_runtime_types.h`` - ``scr_anim_t`` packing and animation registry constants. * - ``src/scripting/script_compile_load.c`` - ``#using_animtree`` handling, identifier validation, and tree lookup. * - ``src/scripting/script_code_emit.c`` - ``%`` and ``#animtree`` emission. * - ``src/qcommon/com_parse.c`` - Shared comment and token handling. * - ``src/client/cgame/animation/anim_tree_load.c`` - Client tree load entry. * - ``src/bg/bg_animation_tree_lookup.c`` - Game-side tree lookup. * - ``animtrees/generic_human.atr`` - Stock sync, ``complete``, comment, and empty-group examples. * - ``animtrees/multiplayer.atr`` - Stock player tree with ``complete loopsync`` and ``complete nonloopsync`` groups. * - ``animtrees/bomber_anim.atr`` - Stock named root groups. * - ``animtrees/trenches_truck_dummies.atr`` - Stock ``loopsync`` group. * - ``animtrees/stalingrad_runner.atr`` - Stock single-leaf tree.