BuildTable & EntityBuilder System - Kamzik123/AnvilToolkit-Resources GitHub Wiki

This tutorial explains how the BuildTable and EntityBuilder systems work under the hood in Assassin's Creed games — how they assemble game entities from modular, swappable parts using a table-driven architecture.

The BuildTable system is part of the AnvilNext engine and is largely consistent across titles (AC3, AC4, Rogue, Unity, Syndicate, Origins, Odyssey, Valhalla, Mirage, etc.). While specific field names or features may vary slightly between engine revisions, the core concepts — columns, rows, references, handles, tags, and row selection — remain the same.

All examples in this tutorial are taken from real exported AC4: Black Flag game files using AnvilToolkit, but the patterns apply to any Assassin's Creed title using the AnvilNext engine. Most examples use the Schema-Based Exporter format; a dedicated section covers the differences with the Standard Exporter used for pre-Origins games.

A note on Path vs ID: Throughout the XML examples you'll see Path attributes on References, Handles, and ObjectPtrs. These paths are not used by the game engine — the engine locates and loads all files exclusively by their numeric ID. The Path values are filled in by AnvilToolkit as a human-readable convenience so you can tell which file is being referenced. When modding, the ID is what matters.


Table of Contents


Core Concept: What is a BuildTable?

Think of a BuildTable as a spreadsheet that the game engine reads at runtime to assemble an entity (a character, a ship, a sound configuration, etc.) from interchangeable parts.

  • Columns define what kind of thing can be swapped (a skeleton, a graphic object, a material, a sound set, etc.)
  • Rows define the actual options available for those columns (a character's hidden blades skeleton, a body mesh, a specific material, etc.)
  • The engine picks a row (based on tags, weights, or explicit selection) and reads across the columns to get all the parts it needs.

This is what makes the Anvil engine's asset system so modular — you can swap an entire outfit by selecting a different row, without touching the entity template itself.


Anatomy of a BuildTable

Here's a simplified, annotated version of a real BuildTable — a character's Hidden Blades table from AC4 (CHR_P_EdwardKenway_HiddenBlades.BuildTable). The structure is identical in other AC games, only the asset paths and IDs change:

<Object ID="41675803805" Type="BuildTable" IsManaged="true">

    <!-- COLUMNS: Define the "slots" (what types of data each column holds) -->
    <DynamicSmallArray Name="Columns" Type="BaseObject">
        <!-- Column 1: expects a Skeleton reference -->
        <BaseObject Type="BuildColumn">
            <Unsigned32 Name="ColumnID">1</Unsigned32>
            <DynamicProperties>
                <DynamicProperty Name="Type" BaseType="Reference" ObjectType="Skeleton" />
            </DynamicProperties>
        </BaseObject>

        <!-- Column 2: expects a GraphicObject reference -->
        <BaseObject Type="BuildColumn">
            <Unsigned32 Name="ColumnID">2</Unsigned32>
            <DynamicProperties>
                <DynamicProperty Name="Type" BaseType="Reference" ObjectType="GraphicObject" />
            </DynamicProperties>
        </BaseObject>
    </DynamicSmallArray>

    <!-- ROWS: The actual data options -->
    <DynamicSmallArray Name="Rows" Type="BaseObject">
        <!-- Row 0: Standard hidden blades (Weight=1, so this is the default) -->
        <BaseObject Type="BuildRow">
            <Float Name="Weight">1</Float>
            <BaseObject Name="Tag" Type="BuildTag">
                <Unsigned32 Name="EngineTagSave">3221187564</Unsigned32>
            </BaseObject>
            <DynamicProperties>
                <!-- Column "1" data: the Skeleton file -->
                <DynamicProperty Name="1" BaseType="Reference" ObjectType="Skeleton">
                    <Reference Type="Skeleton" Path="...\Addon_Human_Weapon_HiddenBlade.Skeleton">
                        5910554062
                    </Reference>
                </DynamicProperty>
                <!-- Column "2" data: the GraphicObject file -->
                <DynamicProperty Name="2" BaseType="Reference" ObjectType="GraphicObject">
                    <Reference Type="GraphicObject" Path="...\CHR_P_EdwardKenway_HiddenBlades.LODSelector">
                        31080645697
                    </Reference>
                </DynamicProperty>
            </DynamicProperties>
        </BaseObject>

        <!-- Row 1: No hidden blades variant (Weight=0, tag-activated only) -->
        <BaseObject Type="BuildRow">
            <Float Name="Weight">0</Float>
            <BaseObject Name="Tag" Type="BuildTag">
                <Unsigned32 Name="EngineTagSave">386527440</Unsigned32>
            </BaseObject>
            <DynamicProperties />  <!-- Empty: no skeleton, no mesh -->
        </BaseObject>
    </DynamicSmallArray>

    <!-- ROW SELECTOR: Default selection configuration -->
    <BaseObject Name="DefaultSelections" Type="RowSelector">
        ...
    </BaseObject>
</Object>

Let's break each part down.


BuildColumns — Defining What Can Be Swapped

A BuildColumn is a slot definition. It doesn't hold data itself — it declares:

  1. What type of data goes in this slot (via DynamicProperties)
  2. A unique ColumnID (a number that rows use to map their data back to this column)
  3. When the data gets applied (via the Pass enum)
  4. Where on the target entity the data gets written (via TargetProperty / PropertyPath)

Here's a complete BuildColumn as it appears in the game files:

<BaseObject ID="41675803806" Type="BuildColumn">
    <!-- WHEN to apply: AP_AllPasses means this applies during every build pass -->
    <Enum Name="Pass" Type="ApplyPass" Value="AP_AllPasses">6</Enum>

    <!-- WHERE to write the data on the target entity (empty = root level) -->
    <BaseObject Name="TargetProperty" Type="PropertyPath">
        <DynamicSmallArray Name="PathNodes" Type="BaseObject" />
        <Bool Name="TargetMustBeUnique">True</Bool>
        <Bool Name="WholeArray">False</Bool>
    </BaseObject>

    <!-- Unique ID for this column — rows reference this number -->
    <Unsigned32 Name="ColumnID">1</Unsigned32>

    <!-- Masking for conditional column application -->
    <Unsigned32 Name="ColumnMask">0</Unsigned32>
    <Unsigned32 Name="ColumnMaskToReject">0</Unsigned32>

    <!-- Back-reference to the parent BuildTable -->
    <ObjectPtr Name="Table" PointerType="External"
        Path="...\CHR_P_EdwardKenway_HiddenBlades.BuildTable">
        41675803805
    </ObjectPtr>

    <!-- TYPE DECLARATION: This column expects a Skeleton reference -->
    <DynamicProperties>
        <DynamicProperty Name="Type" ArrayType="Bool"
            BaseType="Reference" ObjectType="Skeleton" Flags="00000000">
            <Reference Type="Skeleton" PointerType="Null" />
        </DynamicProperty>
    </DynamicProperties>
</BaseObject>

Key fields:

Field Purpose
ColumnID Unique number within this table. Rows use this as their DynamicProperty Name to put data into this column.
Pass Controls when during entity construction this column's data is applied (see ApplyPass).
TargetProperty A PropertyPath describing where on the entity this data should be written. Empty PathNodes = root level.
ColumnMask Bitmask for conditional column filtering. 0 = no filtering.
ColumnMaskToReject Bitmask of conditions that should exclude this column.
DynamicPropertiesType Declares the data type this column accepts (Skeleton, GraphicObject, Material, BuildTable, SoundSet, etc.).

BuildRows — The Actual Data Options

A BuildRow is one complete set of values for every column in the table. Think of it as a row in a spreadsheet — one option that can be selected.

<BaseObject ID="41675803808" Type="BuildRow">
    <!-- Back-reference to parent table -->
    <ObjectPtr Name="Table" PointerType="External"
        Path="...\CHR_P_EdwardKenway_HiddenBlades.BuildTable">
        41675803805
    </ObjectPtr>

    <!-- Selection weight (1 = default/preferred, 0 = only if tag-matched) -->
    <Float Name="Weight">1</Float>

    <!-- This row's unique tag identifier -->
    <BaseObject Name="Tag" Type="BuildTag">
        <Unsigned32 Name="EngineTagSave">3221187564</Unsigned32>
    </BaseObject>

    <!-- Tags this row is valid for (empty = always valid) -->
    <BaseObject Name="PossibleTags" Type="BuildTags">
        <DynamicSmallArray Name="Tags" Type="BaseObject" />
    </BaseObject>

    <!-- THE ACTUAL DATA — one DynamicProperty per column -->
    <DynamicProperties>
        <!-- Data for ColumnID 1 (Skeleton) -->
        <DynamicProperty Name="1" BaseType="Reference" ObjectType="Skeleton">
            <Reference Type="Skeleton" PointerType="External" ReferenceType="0"
                Path="DataPC\...\Addon_Human_Weapon_HiddenBlade.Skeleton">
                5910554062
            </Reference>
        </DynamicProperty>

        <!-- Data for ColumnID 2 (GraphicObject) -->
        <DynamicProperty Name="2" BaseType="Reference" ObjectType="GraphicObject">
            <Reference Type="GraphicObject" PointerType="External" ReferenceType="0"
                Path="DataPC_extra_chr\...\CHR_P_EdwardKenway_HiddenBlades.LODSelector">
                31080645697
            </Reference>
        </DynamicProperty>
    </DynamicProperties>
</BaseObject>

The critical connection: The DynamicProperty Name attribute in a row matches the ColumnID of a column. This is how the engine knows which data goes into which slot:

Column with ColumnID="1"  ←→  Row's DynamicProperty Name="1"
Column with ColumnID="2"  ←→  Row's DynamicProperty Name="2"

How Columns and Rows Work Together

Visualizing the Hidden Blades BuildTable as an actual table:

Column 1 (Skeleton) Column 2 (GraphicObject)
Row 0 (Weight=1, Tag: 3221187564) Addon_Human_Weapon_HiddenBlade.Skeleton CHR_P_EdwardKenway_HiddenBlades.LODSelector
Row 1 (Weight=0, Tag: 386527440) (empty) (empty)

When the engine builds the character's hidden blades:

  1. It looks at the table and picks a row (Row 0 by default, since Weight=1)
  2. It reads across the columns: "Column 1 needs a Skeleton → use Addon_Human_Weapon_HiddenBlade.Skeleton"
  3. "Column 2 needs a GraphicObject → use CHR_P_EdwardKenway_HiddenBlades.LODSelector"
  4. Both assets get loaded and attached to the entity

If the engine activates tag 386527440 instead, Row 1 is selected — and since it has no data, the hidden blades are effectively removed. This same pattern is used across all AC games — from toggling weapon visibility to swapping entire outfits.

This is how BuildRows enable swapping: you add a new row with different file references in the same column slots, and the engine seamlessly substitutes one set of assets for another.


References vs Handles — Two Ways to Point at Files

The AnvilNext engine has two fundamentally different ways to point at external files. Understanding the distinction is essential for modding any AC game.

Reference (FileReference)

A Reference tells the engine to load a file from disk. The engine will find the file, read it, deserialize it, and make it available. It is a loading instruction.

<Reference Type="Skeleton" PointerType="External" ReferenceType="0"
    Path="DataPC\Game Bootstrap Settings\Addon_Human_Weapon_HiddenBlade.Skeleton">
    5910554062
</Reference>

Key characteristics:

  • PointerType="External" — the file is external to this document
  • ReferenceType="0" — standard file reference (triggers a file load)
  • The numeric ID (5910554062) is what the engine actually uses to find and load the file — IDs are the only thing that matters to the engine
  • The Path attribute (e.g. DataPC\...\Addon_Human_Weapon_HiddenBlade.Skeleton) is not used by the engine at all — it is filled in by AnvilToolkit so that humans can see which file is being referenced. The engine resolves everything purely by ID.
  • The file does NOT need to already be loaded — the Reference itself causes it to be loaded

References are used inside BuildRow DynamicProperties to point to the actual assets (skeletons, meshes, materials, sound sets, etc.) that make up an entity.

Handle

A Handle assumes the file is already loaded (or will be loaded by something else). It is a lookup, not a loading instruction.

<Handle Name="Table" Type="BuildTable" PointerType="Inlined"
    Path="DataPC\Game Bootstrap Settings\CHR_Weapon_AddOn.BuildTable">
    451808641
</Handle>

Key characteristics:

  • PointerType="Inlined" — the engine expects this object to already be in memory
  • No ReferenceType attribute — it's not triggering a load
  • Used when something else (a Reference elsewhere, or the game's boot process) has already loaded the target file
  • If the target isn't loaded, the game will crash. Handles do not gracefully fail — the engine assumes the asset is already in memory and will hard-crash if it isn't.
  • Like References, the engine resolves Handles by ID only — the Path attribute is a toolkit convenience for human readability

When Each is Used

Context Uses Reference Uses Handle
BuildRow data (actual assets) Yes — loads the skeleton, mesh, material, etc. Rare — only if the asset is already prefetched (see note below)
RowSelector → Selections → Table No Yes — the table is already loaded via the Tables array
EntityBuilder → Tables array Yes — loads the sub-tables No
EntityBuilder → Template Yes — loads the base entity template No
RowSelector → AssociatedEntityBuilder No Yes — the EntityBuilder is already the current context
MaterialPropertyPathNodeSolver → TargetMaterial No Yes — material is expected to already exist

Note: Handles can appear in BuildRow data in some cases — when the game's prefetching system has already loaded the asset into memory ahead of time. For modding purposes, it's safest to stick with References in BuildRow data unless you're certain the asset will be prefetched.

Null Pointers and Handle ID=0

References can be null — this is safe and commonly used as a type declaration in columns:

<!-- Null Reference (used as type declaration in columns) -->
<Reference Type="Skeleton" PointerType="Null" />

Handles can also have their ID set to 0, but only in certain contexts:

<!-- Safe: TargetMaterialTemplate is optional — ID 0 means "no template" -->
<Handle Name="TargetMaterialTemplate" Type="MaterialTemplate" PointerType="Inlined" Path="0">0</Handle>

<!-- Safe: no associated EntityBuilder for this table -->
<Handle Name="AssociatedEntityBuilder" Type="EntityBuilder" PointerType="Inlined" Path="0">0</Handle>

These work because the engine only references these handles — it doesn't try to load an asset from them. The TargetMaterialTemplate in a MaterialPropertyPathNodeSolver, for example, is optional: setting it to 0 simply means "don't filter by material template."

Warning: If the engine actually tries to use a Handle with ID 0 to load or access an asset — for example, adding a Handle with ID 0 in a BuildTable's RowSelection — the game will crash. A Handle ID of 0 is only safe when the field is purely informational or optional. Any Handle that the engine will actively resolve to load or look up an asset must point to a valid, already-loaded ID.

ObjectPtr and Link — Other Pointer Types

There's also ObjectPtr and Link for internal references:

<!-- ObjectPtr: points to an object, can be External, Inlined, or Null -->
<ObjectPtr Name="Table" Type="" PointerType="External"
    Path="...\CHR_P_EdwardKenway_HiddenBlades.BuildTable">
    41675803805
</ObjectPtr>

<!-- Link: points to another object within the same file -->
<ObjectPtr PointerType="Link">
    <Link Path="8063536875">8063536875</Link>
</ObjectPtr>

The RowSelector — Choosing Which Row to Use

The RowSelector (found in the DefaultSelections field of every BuildTable) controls which row is active by default — and can also select rows in other tables that this one depends on.

Basic Structure

<BaseObject Name="DefaultSelections" Type="RowSelector">
    <!-- Tags to match against rows' PossibleTags -->
    <BaseObject Name="SelectedTags" Type="BuildTags">
        <DynamicSmallArray Name="Tags" Type="BaseObject">
            <BaseObject Type="BuildTag">
                <Unsigned32 Name="EngineTagSave">3948373731</Unsigned32>
            </BaseObject>
        </DynamicSmallArray>
    </BaseObject>

    <!-- Column mask filter (0 = use all columns) -->
    <Unsigned32 Name="BuildColumnMask">0</Unsigned32>

    <!-- Seed for deterministic random selection when weights are involved -->
    <Unsigned32 Name="StableRandomSeed">0</Unsigned32>

    <!-- Explicit row selections in OTHER tables -->
    <DynamicSmallArray Name="Selections" Type="ObjectPtr">
        ...RowSelection entries...
    </DynamicSmallArray>

    <!-- Which EntityBuilder owns this selector -->
    <Handle Name="AssociatedEntityBuilder" Type="EntityBuilder" ... />

    <!-- Additional tables to consider -->
    <DynamicSmallArray Name="AdditionnalTables" Type="Handle" />
</BaseObject>

RowSelection — Explicit Row Picks in Other Tables

The Selections array is where the RowSelector specifies which row to use in other BuildTables. Each entry is a RowSelection:

<ObjectPtr Type="RowSelection" PointerType="Inlined">
    <!-- WHICH table (via Handle — must already be loaded) -->
    <Handle Name="Table" Type="BuildTable" PointerType="Inlined"
        Path="DataPC\Game Bootstrap Settings\CHR_Weapon_AddOn.BuildTable">
        451808641
    </Handle>

    <!-- WHICH row index in that table (0-based) -->
    <Unsigned32 Name="SelectedRow">9</Unsigned32>

    <!-- Optional: which tag to use for further sub-selections -->
    <BaseObject Name="SelectedTag" Type="BuildTag">
        <Unsigned32 Name="EngineTagSave">0</Unsigned32>
    </BaseObject>
</ObjectPtr>

This says: "In the CHR_Weapon_AddOn.BuildTable, use row index 9."

Real example from an AC4 EntityBuilder (Edward Kenway's Foreigner outfit) — the RowSelector pre-selects rows in 6 different tables:

RowSelection Target Table Selected Row
1 CHR_Weapon_AddOn.BuildTable Row 9
2 DataAI_CharacterDefinition-Faction.BuildTable Row 2
3 CHR_Sound_Switch_Dependencies.BuildTable Row 1
4 CHR_Sounbank_option_ALLBanks.BuildTable Row 4
5 (internal table 9880647215) Row 14
6 CHR_Sound_Options_HumanSound_CharacterSelector.BuildTable Row 14

This is how one EntityBuilder configures an entire character's weapons, faction, sounds, and sound banks all in one place — by picking specific rows in each dependency table.

How Selection Actually Works

The engine resolves which row to use through this priority chain:

  1. Explicit RowSelection — if the RowSelector has a Selections entry for this table, that row index wins.
  2. Tag matching — if SelectedTags contains tags, the engine looks for rows whose PossibleTags or Tag match. A row's PossibleTags list defines which tags it's eligible for.
  3. Weight-based selection — among eligible rows, Weight determines probability. Weight=1 is the default. Weight=0 means "only if explicitly tag-matched." Multiple rows with Weight > 0 get random selection (optionally deterministic via StableRandomSeed).
  4. ColumnMask filteringBuildColumnMask can restrict which columns are actually applied from the selected row. 0 = apply all columns.

SelectedTags vs Row Tags

  • RowSelector.SelectedTags — "I want rows that match these tags"
  • BuildRow.Tag — "I am identified by this tag" (the row's unique identity)
  • BuildRow.PossibleTags — "I am valid when any of these tags are active" (eligibility list)

When the engine evaluates: it checks if the RowSelector's SelectedTags intersect with a row's PossibleTags. If they do, that row becomes a candidate.


EntityBuilder — BuildTable's Big Brother

An EntityBuilder is a specialized BuildTable that adds the ability to:

  1. Reference a Template entity (the base object to build upon)
  2. Include an array of sub-Tables (other BuildTables that contribute to the build)
  3. Override template properties via TemplateOverrides
<Object ID="32235282346" Type="EntityBuilder" IsManaged="true">

    <!-- ═══ Inherited from BuildTable ═══ -->
    <DynamicSmallArray Name="Columns" Type="BaseObject">
        <!-- Column 44: CharacterActionSet -->
        <!-- Column 45: Material (with PropertyPath + MaterialPropertyPathNodeSolver) -->
    </DynamicSmallArray>

    <DynamicSmallArray Name="Rows" Type="BaseObject">
        <!-- Row with data for columns 44 and 45 -->
    </DynamicSmallArray>

    <BaseObject Name="DefaultSelections" Type="RowSelector">
        <!-- RowSelections for 6 external tables -->
    </BaseObject>

    <!-- ═══ EntityBuilder-specific fields ═══ -->

    <!-- The base entity template this builder starts from -->
    <Reference Name="Template" Type="Entity" PointerType="External" ReferenceType="0"
        Path="DataPC_extra_chr\CHR_P_EdwardKenway_Default\CHR_P_BaseEntity_Male.Entity">
        420979552
    </Reference>

    <!-- Template property overrides (usually empty) -->
    <DynamicSmallArray Name="TemplateOverrides" Type="BaseObject" />

    <!-- Sub-tables that contribute additional build data (loaded via References) -->
    <DynamicSmallArray Name="Tables" Type="Reference">
        <Reference Type="BuildTable" Path="...\CHR_Inventory.BuildTable">1593930299</Reference>
        <Reference Type="BuildTable" Path="...\Player_Faction.BuildTable">5357152689</Reference>
        <Reference Type="BuildTable" Path="...\CHR_Sound_.BuildTable">4325172614</Reference>
        <Reference Type="BuildTable" Path="...\CHR_Sounbank_option_ALLBanks.BuildTable">5508311500</Reference>
        <Reference Type="BuildTable" Path="...\CHR_P_EdwardKenway_Foreigner_VisualMaster.BuildTable">32902696471</Reference>
    </DynamicSmallArray>
</Object>

The Build Chain

EntityBuilders create a hierarchy:

EntityBuilder (CHR_P_EdwardKenway_Foreigner)
 ├── Template: CHR_P_BaseEntity_Male.Entity
 ├── Own Columns/Rows: CharacterActionSet, Material
 ├── Sub-Tables (via Tables array):
 │   ├── CHR_Inventory.BuildTable
 │   ├── Player_Faction.BuildTable
 │   ├── CHR_Sound_.BuildTable
 │   ├── CHR_Sounbank_option_ALLBanks.BuildTable
 │   └── CHR_P_EdwardKenway_Foreigner_VisualMaster.BuildTable
 │        ├── Column 28 → CHR_P_EdwardKenway_Default_Head.BuildTable
 │        ├── Column 30 → CHR_P_EdwardKenway_Regular4_Body.BuildTable
 │        └── Column 33 → CHR_P_EdwardKenway_HiddenBlades.BuildTable
 │                          ├── Column 1: Skeleton
 │                          └── Column 2: GraphicObject
 └── RowSelector: Pre-selects rows in Weapon, Faction, Sound tables

Notice how BuildTables can reference other BuildTables in their row data. The VisualMaster table's columns hold References to the Head, Body, and HiddenBlades BuildTables — which in turn hold References to the actual asset files. This creates a tree of tables that the engine walks recursively.


PropertyPath — Targeting Deep Properties

When a BuildColumn needs to modify a specific property deep inside an entity (not at the root level), it uses a PropertyPath with PropertyPathNode entries to navigate the object hierarchy.

Example: Setting the Faction on an Entity

From Player_Faction.BuildTable:

<BaseObject Name="TargetProperty" Type="PropertyPath">
    <DynamicSmallArray Name="PathNodes" Type="BaseObject">
        <!-- Step 1: Navigate into "Components" array, find by class ID -->
        <BaseObject Type="PropertyPathNode">
            <Unsigned16 Name="Index">65535</Unsigned16>           <!-- 65535 = search all -->
            <Enum Name="Condition" Value="NC_CLASSID">2</Enum>    <!-- Match by class -->
            <Hash32 Name="TargetPropertyID" HashValue="Components" />
            <Unsigned64 Name="ConditionData">7026055331649904021</Unsigned64>  <!-- EntityAI class ID -->
        </BaseObject>

        <!-- Step 2: Navigate into "EntityAI" object -->
        <BaseObject Type="PropertyPathNode">
            <Unsigned16 Name="Index">0</Unsigned16>
            <Enum Name="Condition" Value="NC_NOCONDITION">0</Enum>
            <Hash32 Name="TargetPropertyID" HashValue="Faction" />
        </BaseObject>

        <!-- Step 3: Target the "Faction" property -->
        <BaseObject Type="PropertyPathNode">
            <Hash32 Name="TargetPropertyID" HashValue="Faction" />
        </BaseObject>
    </DynamicSmallArray>
</BaseObject>

This navigates: Entity → Components[class==EntityAI] → Faction — and the row's data for this column gets written to that exact property.

Special PropertyPathNode Fields

Field Purpose
Index Which array element to target. 65535 = search/match all elements. 0 = first element.
Condition NC_NOCONDITION (0) = no filter. NC_CLASSID (2) = match by class ID.
ConditionData The class ID to match when Condition = NC_CLASSID.
TargetPropertyID Hash of the property name to navigate into.
NodeSolver Optional solver for complex resolution (e.g., MaterialPropertyPathNodeSolver for finding materials by blend mode or flags).

ApplyPass — When Modifications Happen

Each BuildColumn has a Pass that controls when during entity construction its data is applied:

Pass Value When It Runs
AP_ModificationsBeforeInitialize 2 Before the entity is initialized — for data that must exist before init (e.g., sound banks)
AP_PropertyModifications 4 During the property modification phase
AP_PropertyModifications2 5 A second property modification phase (for changes that depend on earlier modifications)
AP_AllPasses 6 Applied during every pass — safe default for most data

The pass system ensures that dependencies are resolved in order. For example, a sound bank must be loaded (AP_ModificationsBeforeInitialize) before a sound set can reference it (AP_PropertyModifications2).


Tags and Weights — Conditional Row Selection

Weights

  • Weight = 1 — This row is the default. If no tags are active and no explicit selection is made, this row wins.
  • Weight = 0 — This row is never selected randomly. It can only be activated by tag matching or explicit RowSelection.
  • Weight = 0.2 — (seen in enemy crew sound tables) Each row has a 20% chance of being selected, creating randomized variety.

Tags (EngineTagSave)

Tags are 32-bit hash values that identify build variants. They're used throughout the system:

  • A BuildRow's Tag is that row's identity — its unique name in the table.
  • A BuildRow's PossibleTags lists which active tags make this row eligible.
  • A RowSelector's SelectedTags specify which tags the selector is looking for.

Example from AC4's ship system (Ship_HasName.BuildTable) — the same tag-based selection pattern is used for NPC variants, equipment, and other conditional content across all AC games:

Row 0 (Weight=1): Default ship — no specific tags needed
Row 1 (Weight=0): PossibleTags = [tag for "La Dama Negra"] → specific ship BuildTable
Row 2 (Weight=0): PossibleTags = [tag for "HMS Prince"] → specific ship BuildTable
Row 3 (Weight=0): PossibleTags = [tag for "Royal Fortune"] → specific ship BuildTable
...

The default row (Weight=1) handles generic ships. When the engine needs a specific named ship, it activates the corresponding tag, which matches a row's PossibleTags, overriding the default.


Standard Exporter vs Schema-Based Exporter

AnvilToolkit provides two ways to export BuildTable and EntityBuilder files into XML. The underlying data is identical — only the XML representation differs. Knowing which format you're looking at is important because field names and element structures change between them.

  • Standard Exporter — The default for all pre-Origins games (AC3, AC4, Rogue, Unity, Syndicate). Uses human-friendly element names (e.g., <BuildTable>, <BuildColumn>, <BuildRow>, <FileReference>). Exported as UTF-16 with type="0" in the XML header.
  • Schema-Based Exporter — The only option for Origins and later games. Also available for pre-Origins games as an alternative. Uses a direct reflection of the engine's internal schema (e.g., <Object Type="BuildTable">, <BaseObject Type="BuildColumn">, <Reference>). Exported as UTF-8 with type="1" in the XML header.

All examples earlier in this tutorial use the Schema-Based format. Here's how the same data looks in each format:

Side-by-Side: A BuildColumn

Schema-Based Exporter:

<BaseObject ID="41675803806" Type="BuildColumn">
    <Enum Name="Pass" Type="ApplyPass" Value="AP_AllPasses">6</Enum>
    <Unsigned32 Name="ColumnID">1</Unsigned32>
    <Unsigned32 Name="ColumnMask">0</Unsigned32>
    <Unsigned32 Name="ColumnMaskToReject">0</Unsigned32>
    <DynamicProperties>
        <DynamicProperty Name="Type" BaseType="Reference" ObjectType="Skeleton">
            <Reference Type="Skeleton" PointerType="Null" />
        </DynamicProperty>
    </DynamicProperties>
</BaseObject>

Standard Exporter:

<BuildColumn ID="41675803806" Index="1">
    <Value Name="Pass" Type="Enum" EnumName="BuildColumnPass" ValueName="AllPasses">6</Value>
    <Value Name="MaskToApplyColumn" Type="Int32">0</Value>
    <Value Name="MaskToIgnoreColumn" Type="Int32">0</Value>
    <List Name="Components" Type="DynamicProperty">
        <DynamicProperty>
            <Value Name="DataType" Type="UInt32" HashName="Skeleton">615435132</Value>
            <Value Name="Type" Type="UInt32">1835008</Value>
            <Reference>
                <FileReference Name="Value" IsGlobal="0" Path="0">0</FileReference>
            </Reference>
        </DynamicProperty>
    </List>
</BuildColumn>

Side-by-Side: A BuildRow with Data

Schema-Based Exporter:

<BaseObject Type="BuildRow">
    <Float Name="Weight">1</Float>
    <BaseObject Name="Tag" Type="BuildTag">
        <Unsigned32 Name="EngineTagSave">3221187564</Unsigned32>
    </BaseObject>
    <DynamicProperties>
        <DynamicProperty Name="1" BaseType="Reference" ObjectType="Skeleton">
            <Reference Type="Skeleton" PointerType="External" ReferenceType="0"
                Path="DataPC\...\Addon_Human_Weapon_HiddenBlade.Skeleton">5910554062</Reference>
        </DynamicProperty>
    </DynamicProperties>
</BaseObject>

Standard Exporter:

<BuildRow ID="41675803808">
    <Value Name="Weight" Type="Single">1</Value>
    <BuildTag Name="Tag" ID="0" Tag="3221187564" TagName="xBFFF6BEC" />
    <List Name="Components" Type="DynamicProperty">
        <DynamicProperty Index="1">
            <Value Name="DataType" Type="UInt32" HashName="Skeleton">615435132</Value>
            <Value Name="Type" Type="UInt32">1835008</Value>
            <Reference>
                <FileReference Name="Value" IsGlobal="0"
                    Path="DataPC\...\Addon_Human_Weapon_HiddenBlade.Skeleton">5910554062</FileReference>
            </Reference>
        </DynamicProperty>
    </List>
</BuildRow>

Side-by-Side: A Handle

Schema-Based Exporter:

<Handle Name="AssociatedEntityBuilder" Type="EntityBuilder" PointerType="Inlined"
    Path="DataPC_extra_chr\...\CHR_P_EdwardKenway_Foreigner.EntityBuilder">32235282346</Handle>

Standard Exporter:

<Handle Name="AssociatedEntityBuilder">
    <Value Name="Value" Type="UInt64"
        Path="DataPC_extra_chr\...\CHR_P_EdwardKenway_Foreigner.EntityBuilder">32235282346</Value>
</Handle>

Side-by-Side: A RowSelection

Schema-Based Exporter:

<ObjectPtr Type="RowSelection" PointerType="Inlined">
    <Handle Name="Table" Type="BuildTable" PointerType="Inlined"
        Path="DataPC\...\CHR_Weapon_AddOn.BuildTable">451808641</Handle>
    <Unsigned32 Name="SelectedRow">9</Unsigned32>
</ObjectPtr>

Standard Exporter:

<ObjectPtr>
    <RowSelection ID="0">
        <Value Name="BuildTableRef" Type="UInt64"
            Path="DataPC\...\CHR_Weapon_AddOn.BuildTable">451808641</Value>
        <Value Name="SelectedRow" Type="UInt32">9</Value>
    </RowSelection>
</ObjectPtr>

Complete Field Name Mapping

Concept Schema-Based Exporter Standard Exporter
Root element <Object Type="BuildTable"> <BuildTable>
Root element (EB) <Object Type="EntityBuilder"> <EntityBuilder>
Column element <BaseObject Type="BuildColumn"> <BuildColumn>
Column ID <Unsigned32 Name="ColumnID"> Index attribute on <BuildColumn>
Column mask <Unsigned32 Name="ColumnMask"> <Value Name="MaskToApplyColumn">
Column mask reject <Unsigned32 Name="ColumnMaskToReject"> <Value Name="MaskToIgnoreColumn">
Columns array <DynamicSmallArray Name="Columns"> <List Name="BuildColumns">
Row element <BaseObject Type="BuildRow"> <BuildRow>
Rows array <DynamicSmallArray Name="Rows"> <List Name="BuildRows">
Row/Column data <DynamicProperties> <List Name="Components" Type="DynamicProperty">
Row data column link <DynamicProperty Name="1"> (Name = ColumnID) <DynamicProperty Index="1"> (Index = ColumnID)
Data type in DynProp ObjectType="Skeleton" attribute <Value Name="DataType" HashName="Skeleton">
File reference <Reference Type="..." PointerType="External"> <FileReference Name="Value" IsGlobal="0">
Handle <Handle Name="..." Type="..." PointerType="Inlined">ID</Handle> <Handle Name="..."><Value Name="Value" Type="UInt64">ID</Value></Handle>
Tag <Unsigned32 Name="EngineTagSave"> Tag attribute on <BuildTag> element
Tag (with name) (no tag name shown) TagName="xBFFF6BEC" attribute
Pass enum Value="AP_AllPasses" ValueName="AllPasses"
Pass enum (mods) Value="AP_PropertyModifications" ValueName="PropertyModifications1"
PropertyPath <BaseObject Type="PropertyPath"> <PropertyPath>
PropertyPathNode <BaseObject Type="PropertyPathNode"> <PropertyPathNode>
NodeSolver <ObjectPtr Name="NodeSolver" Type="MaterialPropertyPathNodeSolver"> <MaterialPropertyPathNodeSolver>
RowSelection table ref <Handle Name="Table"> <Value Name="BuildTableRef">
Positions array <DynamicSmallArray Name="Positions"> <List Name="EntityPositionSelections">
WholeArray <Bool Name="WholeArray"> <Value Name="SetWholeArray" Type="Bool">
Encoding UTF-8, type="1" UTF-16, type="0"

Which should I use? For pre-Origins games, either format works — AnvilToolkit can import both. The Standard Exporter is more readable at a glance (dedicated element names like <BuildColumn> and <FileReference>), while the Schema-Based Exporter maps directly to the engine's internal class structure. For Origins and later, only the Schema-Based format is available. The concepts, IDs, and data are identical regardless of format.


Practical: How to Add a New File Reference

To add a new asset reference to an existing BuildTable:

Step 1: Add a new BuildColumn (if the table doesn't already have a column for your type)

<BaseObject Type="BuildColumn">
    <Enum Name="Pass" Type="ApplyPass" Value="AP_AllPasses">6</Enum>
    <BaseObject Name="TargetProperty" Type="PropertyPath">
        <DynamicSmallArray Name="PathNodes" Type="BaseObject" />
        <Bool Name="TargetMustBeUnique">True</Bool>
        <Bool Name="WholeArray">False</Bool>
    </BaseObject>
    <!-- Pick a ColumnID not already used in this table -->
    <Unsigned32 Name="ColumnID">3</Unsigned32>
    <Unsigned32 Name="ColumnMask">0</Unsigned32>
    <Unsigned32 Name="ColumnMaskToReject">0</Unsigned32>
    <DynamicSmallArray Name="Positions" Type="BaseObject" />
    <!-- Point back to YOUR BuildTable -->
    <ObjectPtr Name="Table" PointerType="External" Path="YOUR_TABLE_PATH">YOUR_TABLE_ID</ObjectPtr>
    <DynamicProperties>
        <!-- Declare the type (e.g., Material) -->
        <DynamicProperty Name="Type" ArrayType="Bool" BaseType="Reference"
            ObjectType="Material" Flags="00000000">
            <Reference Type="Material" PointerType="Null" />
        </DynamicProperty>
    </DynamicProperties>
</BaseObject>

Step 2: Add data to every existing BuildRow

Each row needs a DynamicProperty whose Name matches the new ColumnID:

<!-- Inside the row's DynamicProperties -->
<DynamicProperty Name="3" ArrayType="Bool" BaseType="Reference"
    ObjectType="Material" Flags="00000000">
    <Reference Type="Material" PointerType="External" ReferenceType="0"
        Path="DataPC_extra_chr\YourFolder\YourMaterial.Material">
        YOUR_MATERIAL_ID
    </Reference>
</DynamicProperty>

Remember: Use a Reference (not a Handle) here, because the BuildRow's job is to load the file. The engine reads this Reference and loads the material from disk.


Practical: How to Add a New Swappable Row

To add a new variant (e.g., a new outfit option):

Step 1: Create the BuildRow

<BaseObject Type="BuildRow">
    <!-- Back-reference to parent table -->
    <ObjectPtr Name="Table" PointerType="External" Path="YOUR_TABLE_PATH">YOUR_TABLE_ID</ObjectPtr>

    <!-- Weight=0 means tag-activated only. Use Weight=1 to make it the new default -->
    <Float Name="Weight">0</Float>

    <!-- Unique tag for this row -->
    <BaseObject Name="Tag" Type="BuildTag">
        <Unsigned32 Name="EngineTagSave">YOUR_UNIQUE_TAG_HASH</Unsigned32>
    </BaseObject>

    <!-- Which tags activate this row (empty = must be explicitly selected) -->
    <BaseObject Name="PossibleTags" Type="BuildTags">
        <DynamicSmallArray Name="Tags" Type="BaseObject">
            <BaseObject Type="BuildTag">
                <Unsigned32 Name="EngineTagSave">YOUR_UNIQUE_TAG_HASH</Unsigned32>
            </BaseObject>
        </DynamicSmallArray>
    </BaseObject>

    <DynamicSmallArray Name="Dependencies" Type="ObjectID" />

    <!-- One DynamicProperty per column in the table -->
    <DynamicProperties>
        <DynamicProperty Name="1" ArrayType="Bool" BaseType="Reference"
            ObjectType="Skeleton" Flags="00000000">
            <Reference Type="Skeleton" PointerType="External" ReferenceType="0"
                Path="DataPC\...\YourCustomSkeleton.Skeleton">
                YOUR_SKELETON_ID
            </Reference>
        </DynamicProperty>
        <DynamicProperty Name="2" ArrayType="Bool" BaseType="Reference"
            ObjectType="GraphicObject" Flags="00000000">
            <Reference Type="GraphicObject" PointerType="External" ReferenceType="0"
                Path="DataPC_extra_chr\...\YourCustomMesh.LODSelector">
                YOUR_MESH_ID
            </Reference>
        </DynamicProperty>
    </DynamicProperties>
</BaseObject>

Step 2: Insert it into the Rows array

Add the new <BaseObject Type="BuildRow"> element inside the table's <DynamicSmallArray Name="Rows"> block.

Step 3: (Optional) Update the RowSelector

If you want a parent EntityBuilder to use your new row by default, add a RowSelection to its DefaultSelections:

<ObjectPtr Type="RowSelection" PointerType="Inlined">
    <Handle Name="Table" Type="BuildTable" PointerType="Inlined"
        Path="YOUR_TABLE_PATH">YOUR_TABLE_ID</Handle>
    <!-- Index of your new row (0-based) -->
    <Unsigned32 Name="SelectedRow">2</Unsigned32>
    <BaseObject Name="SelectedTag" Type="BuildTag">
        <Unsigned32 Name="EngineTagSave">0</Unsigned32>
    </BaseObject>
</ObjectPtr>

Putting It All Together — A Character Build Chain (AC4 Example)

Here's how the engine assembles a character entity from scratch, using Edward Kenway's "Foreigner" outfit from AC4 as a concrete example. The same hierarchical pattern applies in every AC game — only the specific table and asset names differ:

1. Game loads EntityBuilder: CHR_P_EdwardKenway_Foreigner.EntityBuilder

2. EntityBuilder loads its Template:
   → CHR_P_BaseEntity_Male.Entity (the base male character)

3. EntityBuilder loads its sub-Tables (via References — these trigger file loads):
   → CHR_Inventory.BuildTable
   → Player_Faction.BuildTable
   → CHR_Sound_.BuildTable
   → CHR_Sounbank_option_ALLBanks.BuildTable
   → CHR_P_EdwardKenway_Foreigner_VisualMaster.BuildTable

4. The VisualMaster BuildTable's row data references MORE BuildTables:
   → Column 28: CHR_P_EdwardKenway_Default_Head.BuildTable
   → Column 30: CHR_P_EdwardKenway_Regular4_Body.BuildTable
   → Column 33: CHR_P_EdwardKenway_HiddenBlades.BuildTable

5. Each leaf table's row data contains the actual asset References:
   → Head table: head skeleton, head mesh, head materials...
   → Body table: body skeleton, body mesh, body materials...
   → HiddenBlades table: blade skeleton, blade mesh

6. The RowSelector in the EntityBuilder pre-selects rows in shared tables:
   → CHR_Weapon_AddOn: row 9 (specific weapon loadout)
   → Faction: row 2 (player faction)
   → Sound: rows 1, 4, 14 (specific voice/sound config)

7. The engine applies all selected data through the Pass system:
   AP_ModificationsBeforeInitialize → sound banks loaded first
   AP_PropertyModifications        → property overrides applied
   AP_PropertyModifications2       → secondary overrides (materials, sounds)
   AP_AllPasses                    → base data (skeletons, meshes, tables)

8. Result: A fully assembled Edward Kenway entity with:
   - Base male body from template
   - Foreigner outfit body mesh + materials
   - Default head
   - Hidden blades (or not, depending on tags)
   - Correct weapon addon
   - Player faction AI
   - Character-specific sound sets and sound banks

Quick Reference Summary

Concept What It Does
BuildTable A table of swappable data. Columns define slots, rows define options.
EntityBuilder A BuildTable that also has a Template entity and sub-Tables. Top of the build hierarchy.
BuildColumn Defines one data slot: its type, when it applies (Pass), and where it writes (PropertyPath).
BuildRow One complete set of values for all columns. Selected by weight, tags, or explicit RowSelection.
ColumnID Links a column definition to row data. Row's DynamicProperty Name must match.
Reference Loads a file from disk. Used in row data for actual assets.
Handle Looks up an already-loaded file. Used in RowSelectors and property solvers.
RowSelector Chooses which rows are active. Can select rows in external tables via RowSelection entries.
BuildTag A 32-bit hash identifying a variant. Tags enable conditional row selection.
Weight Row selection probability. 1 = default, 0 = tag-only, 0.2 = 20% random chance.
PropertyPath Chain of PropertyPathNodes navigating to a deep property on the target entity.
ApplyPass Controls build phase ordering: BeforeInit → Modifications → Modifications2 → AllPasses.
⚠️ **GitHub.com Fallback** ⚠️