Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Archetypes and spawning

An archetype names the components every instance of a kind of entity carries, with their values: the flagship’s player, a boar, a pet. Archetypes are manifold:archetype registry entries, authored in RON under data/archetypes/ and resolved by the data stage. After the stage freezes, each archetype variant compiles to a spawn template, and every archetype entity is placed by spawn_archetype through the entity core (docs/architecture/entity-core.md)’s funnel in one table move, with its durable identity. The design and its rationale are in Talos A (docs/superpowers/specs/2026-09-27-talos-a-entity-core-design.md) §10–§11 and §16; this page is the current contract.

Source: manifold_engine::archetype (crates/manifold-engine/src/archetype/) (format, inheritance, templates, spawn, patches), the server glue in manifold_server_state::archetypes (crates/manifold-server-state/src/archetypes.rs) (presentation components, the anim-setup hook, template install), the player spawn in player_spawn.rs (crates/manifold-server-state/src/player_spawn.rs) and archetype records in entity_persistence.rs (crates/manifold-server-state/src/entity_persistence.rs). Tests: crates/manifold-engine/src/archetype/tests.rs and the archetypes tests in manifold-server-state.

Format

// flagship-game: data/archetypes/boar.ron
Archetype(
    id: "flagship-game:boar",
    parent: Some("flagship-game:quadruped"),
    persistence: Session,
    spatial_layers: ["creature"],
    bounds_radius: 0.7,
    components: {
        "manifold-model:model": (asset: "flagship:creatures/boar"),
        "manifold-anim:anim-setup": (bindings: "flagship:animations/boar"),
        "manifold-anim:hit-eval": (mode: Lazy),
    },
    optional: ["manifold:world-label"],
    variants: [
        (id: "tusker", when: Some(Chance(0.1)), priority: 20,
         patch: { "manifold-model:model": (scale: 1.3) }),
        (id: "common"),
    ],
)
FieldMeaning
idthe entry’s key, in the declaring mod’s namespace
parentat most one; merged in by a derive step
abstracttrue for parents that never spawn (not inherited)
copyabletrue lets Enki’s clipboard and blueprints copy instances; off by default, inherited, and left out of the definition hash when off
persistenceSaved, External or Session (default); a spawn may override it
spatial_layers, bounds_radiusspatial index metadata; a bare layer name means manifold:<name> (built-ins: player, creature, item, projectile, prop)
tagsinitial tags (Talos B’s TagContainer)
componentscomponents every instance carries; () is the component’s default
optionalcomponents an instance may gain at runtime (unioned with the parent’s)
removeinherited components this archetype drops
variantsspawn-time variants; exactly one has no when (the fallback)

A component value is written in the component’s own schema. Postcard components (serde types: Pygmalion’s, manifold-model:model) read through serde over their default value, so unwritten fields keep their defaults. Schema and novel components read field by field; a nested record names dotted fields (eye_offset: (x: 0.0, y: 1.6, z: 0.0)). Registry references are written as keys (bindings: "flagship:animations/boar") and resolve to dense ids; hashed and saved forms keep the key. A novel component whose schema cannot describe its value (an opaque guest payload) takes the default its mod declared (ModApp::add_archetype_with_novel_defaults) and can only be written as (). Bookkeeping (SpawnId, PersistentEntity, ArchetypeRef, EntityOwner) cannot be named. Relationship kinds (manifold-ecs:owned-by) appear as (); their targets come from the spawn.

Inheritance. Resolution walks the chain root first. A child’s component entry merges into the parent’s value field by field (records by field, maps by key, lists and scalars replace); Replace(value) replaces the whole value. Cycles and missing parents are errors at the parent field, with file and line.

Required and forbidden components. Each variant’s list is closed over the components’ requires metadata, with defaults, and checked against every forbids. Removing a component another requires is an error. Components a spawn’s patch adds bring their requirements the same way.

Variants. Variants are tried in descending priority, ties in declaration order; the first whose when holds is chosen. Conditions evaluate over a SpawnContext whose randomness is DerivedRandom(space seed, new durable id, spawn tick, "manifold:archetype-variant"), so a spawn chooses the same variant on every run. A literal Chance(p) is built in; any other when is Talos B’s Condition in the spawn context set (the spawn position is Origin), compiled by the VariantConditionCompiler the gameplay framework installs in the data stage. A caller may name a variant (VariantChoice::Named, /entity spawn key#variant).

Code declarations. ModApp::add_archetype* archetypes are declared as code trees. A data/archetypes/ entry of the same mod with the same key supersedes one, as the flagship’s player.ron supersedes its guest’s declaration, which remains the source of its novel components’ defaults.

Templates and spawning

ArchetypeTemplates::compile (a world resource, Arc<ArchetypeTemplates>, installed by archetypes::install_archetypes on both server boot paths) builds, per variant, a cloneable bundle of the native components (Tracked<C>) and the novel components’ bytes. Native components need spawn operations: the Component derive registers them (Default + Clone); serde components register with register_postcard_spawnable.

spawn_archetype(world, id, params) (immediate), spawn_archetype_in(ctx, …) (a structural context) and spawn_archetype_deferred(commands, …) (the SpawnArchetype funnel command, handle reserved at once) build one bundle in this order, later layers winning:

  1. the template bundle;
  2. spawn bindings: the spawn position, yaw, and anim_seed (DerivedRandom(space seed, durable id, spawn tick, "manifold-anim:anim-seed")) written into the components whose metadata binds them;
  3. the instance Patch and its requirements;
  4. relations (RelationInit::new::<OwnedBy>(owner_ref)), resolved through the identity index or kept by durable id to relink later;
  5. per-session extras (ControlledBy);
  6. bookkeeping: ArchetypeRef { id, variant }, EntityOwner, PersistentEntity (its id taken before building, so the variant and seed derive from it).

The funnel adds SpawnId, runs hooks and places the entity once. spawn_archetype_batch spawns many in parameter order.

Presentation. An archetype names its model in manifold-model:model (public, low rate), its binding table in manifold-anim:anim-setup, and its hit evaluation in manifold-anim:hit-eval (server-only). The server’s on_add hook for anim-setup queues AttachServerAnim, so the entity has its ServerAnim by the end of the flush that spawned it, built at the graph’s entry state as waking builds it. Clients already in scope get an AnimSync; presenting NPCs on clients is Pygmalion E6 with Talos F1.

The player

The flagship’s player is game/flagship-game/data/archetypes/player.ron, designated by set_player_archetype("player"). A game must designate one: both server hosts refuse to boot without it (manifold_server_state::require_player_archetype), since no joining player could be spawned. spawn_player_entity_and_wire_connection spawns the body from it with the spawn position and saved yaw as bindings, a patch of the saved velocity, pitch, motion mode, display name and novel components, ControlledBy as an extra, the player_entity_ids durable id, EntityOwner::Player and Persistence::External. When the server’s catalog holds the game’s player presentation, the patch adds anim-setup (seeded from the PlayerUuid) and the placeholder equipment, and the hook attaches ServerAnim; otherwise AnimBindSystem’s lazy attach covers a catalog that loads later. PlayerRecord is unchanged. A world without templates (a test harness) spawns the body by hand.

Every save of a connected player (native periodic, shutdown and disconnect saves, and the browser flush) builds its record with capture_player_record: the stored record refreshed from the body. Saved novel components whose key is not registered this session (their mod is not installed) are kept as stored, because restore skips rather than loads them; registered ones are rewritten from the body’s DynComponents.

Patches and saves

A Patch is an instance’s component-level difference from its template: components whose bytes differ from the template default (a memcmp), or that the template lacks, and template components it no longer carries. Patch::diff computes it; PatchWire is its wire and save form.

Entity snapshots are version 2. A Saved archetype entity is written as its archetype key, variant id and definition hash, the patch in persisted form (registry references as keys) with the accounting owner, and its links (relationships included). Restore spawns it through spawn_archetype with its old id and the saved variant, applying the patch over the current template, so a changed default reaches every instance that had not changed it. An unknown archetype, variant or codec keeps the record offline and retained. Version 1 snapshots read as full-component records; the next save writes version 2. anim-setup is re-derived at spawn and not saved.

Console

/entity spawn <archetype>[#variant] [x y z] (admin) spawns at the caller’s position, or at the given coordinates, on both hosts. Talos G1 extends /entity.

Limits

  • Column-batch placement (§11.3) is not built: a batch spawns entity by entity through the funnel, each in one placement.
  • Variant conditions see the spawn position, derived randomness and the spawner’s SpawnFacts (sky light, the surface block), which E’s atoms read (population); a biome atom waits for a server-side biome source.
  • The server catalog still imports every model it lists; the archetypes’ model set is checked against it at boot (missing_archetype_models).
  • Spawn replication by patch (A4), hot reload (A2) and composite archetypes (A3) are later phases.