Asset Core Docs

Deterministic world-state engine documentation and API references.

Decision Gate docs

Containers and Assets

Containers are the fundamental storage abstraction in Asset Core. They hold assets with different spatial characteristics depending on their kind. The key idea is that the same transaction model applies across all container types, so you can model diverse domains without changing infrastructure.

Problem this concept solves

Real-world asset management involves diverse storage patterns. A one-size-fits-all data model either overfits or loses critical semantics, especially in spatial systems.

  • Aggregated quantities (account balances, inventory counts)
  • Ordered sequences (equipment slots, ranked lists)
  • Spatial arrangements (warehouse grids, lab plates)

A single storage model cannot efficiently represent all these patterns. Asset Core solves this by providing multiple container kinds, each optimized for its spatial dimension while sharing a common operation interface. This gives you both fidelity and consistency.

Core ideas

Dimensional Classification

The hardest part of building spatial state systems is handling the dimensional mismatch between storage patterns. Account balances are scalar (0D): you care about quantity, not position. Equipment slots are sequential (1D): position matters, but only in a line. Warehouse grids are planar (2D): adjacency and collision matter in two dimensions.

Most systems pick one model and stretch it uncomfortably. Store everything in a relational table and lose spatial semantics (no collision detection, no adjacency queries). Store everything in a graph and pay for structure you don’t need (a balance doesn’t need coordinates). Store everything in a generic key-value store and implement spatial logic in application code (reinventing collision detection for every domain).

AssetCore takes a different approach: provide the RIGHT container for each dimensional need, but keep the operation interface consistent. Containers are classified by the dimensionality of their address space. Each kind adds spatial semantics without changing the transaction envelope:

KindDimensionAddress SpaceExample Use Cases
Balance (balance)0DNone (aggregation only)Balances, totals
Slots (slots)1DSequential indicesEquipment, ordered lists
Grid (grid)2DLattice coordinatesPlates, warehouses
Continuous Line (continuous_line_1d)1DFixed-point coordinate (x)Rails, actuators
Continuous Plane (continuous_grid_2d)2DFixed-point coordinates (x, y)Robot workcells, pick-and-place

This dimensional typing solves a real problem: it prevents you from accidentally treating a scalar balance like a grid (nonsensical) or querying slot positions from a continuous plane (type error). The container kind encodes the spatial contract, and operations validate against it at execution time.

What you gain: the same operations (AddFungible, MoveInstance, etc.) adapt to the container’s geometry automatically. What you give up: the cognitive overhead of learning 5 container kinds instead of 1. We believe this trade makes you faster in the long run because your domain model matches your mental model.

Balances (0D)

Balance containers hold fungible balances. Use them when you only care about quantity and not spatial placement—account ledgers, currency totals, resource pools.

Balances are 0-dimensional: they have no coordinates, no positions, no spatial footprint. A balance is a pure scalar counter. This makes them the right tool for “how much” questions, not “where” questions.

  • Identified by (class_id, key) pairs
  • Quantities aggregate without spatial position (no x/y coordinates)
  • Support add, remove, and transfer operations

Why this matters: If you stored currency in a Grid container, you’d have to pick arbitrary coordinates for each unit. If you stored it in Slots, you’d waste a slot per unit and hit capacity limits instantly. Balances give you unbounded scalar aggregation, which is what currency actually is.

Example use case: A reagent inventory in a lab where you track milliliters of liquid nitrogen. You don’t care WHERE the nitrogen is (it’s in the tank), you care HOW MUCH you have. That’s a balance.

{
  "op": "AddFungible",
  "args": {
    "class_id": 100,
    "key": 1,
    "quantity": 500,
    "location": {
      "container_id": 1001,
      "kind": "balance"
    }
  }
}

What you gain: aggregation without spatial overhead. A balance holding 1,000,000 units is the same cost as a balance holding 1 unit—it’s just a counter. What you give up: spatial queries. You can’t ask “what’s at position (3,5)” for a balance because positions don’t exist. If you need spatial semantics, use Grid or Continuous containers instead.

Slots (1D)

Slot containers hold unique instances in sequential positions. They are ideal for equipment, loadouts, or ordered lists where exclusivity matters more than geometry.

  • Indices from 1 to N (configurable capacity)
  • Each slot holds at most one instance
  • Support place, remove, and swap operations

Example: Equipment slots on a character or positions in a processing queue.

{
  "op": "PlaceInSlot",
  "args": {
    "container_id": 1001,
    "instance_id": 9001,
    "slot_index": 1
  }
}

Grid (2D)

Grid containers add spatial geometry. They are the right choice for warehouses, lab plates, and any environment where adjacency and collision matter.

  • Width x Height lattice of cells
  • Items occupy multiple cells based on shape
  • Support collision detection and adjacency

Example: A 96-well plate or warehouse storage grid.

Grid containers can hold both fungible stacks (with spatial placement) and unique instances.

Continuous Line (1D)

Continuous line containers (continuous_line_1d) store instances along a single fixed-point axis. Fixed-point coordinates guarantee deterministic replay without floating-point drift.

  • Coordinates are deterministic fixed-point integers (no floats)
  • Placements must remain within min/max bounds
  • Collision checks enforce non-overlapping spans

Use cases: linear rails, conveyors with sub-cell precision, and single-axis robotics.

Continuous Plane (2D)

Continuous plane containers (continuous_grid_2d) store instances in a bounded, continuous workspace. They are designed for robotics workcells and continuous planning tasks.

  • Fixed-point x/y coordinates with deterministic rounding
  • Oriented-rectangle collision checks (rotation in millidegrees)
  • Bounds-checked placements, moves, and rotations
  • Instance-only placements (no fungible stacks)

Use cases: robot workcells, pick-and-place tasks, and metric collision planning.

Instances vs. Fungibles

This distinction mirrors the real world: currency is fungible (your $20 bill is interchangeable with mine), equipment is unique (your specific sword has durability, enchantments, and a history). The type system enforces this at the operation level.

Fungible assets are interchangeable quantities. They are represented by class and key pairs and can be split or merged freely.

  • Identified by class and key
  • Operations specify quantities
  • Can be split and merged freely

Why it matters: fungibles can be split and merged freely, which enables operations like Distribute (split quantity across targets) and ConsolidateStacks (merge into one). Trying to “split” an instance is a type error caught at validation time.

Unique instances are individually tracked. They are represented by stable IDs and can be attached or detached to form hierarchies.

  • Have globally unique IDs
  • Cannot be split or merged
  • Can form parent-child hierarchies via attach/detach

Unique instances cannot be split or merged—MoveInstance relocates the entire instance atomically. The parent-child hierarchy for instances (Attach/Detach) models real nesting: a backpack contains a pouch, which contains potions. The hierarchy is reflected in queries, making “what’s in this container recursively?” a first-class operation.

Classes and Shapes

Classes define asset types. They are the canonical identifiers for both fungibles and instances, and they must be registered before use.

  • Provide metadata like names and optional flags
  • Serve as stable identifiers for fungible balances or unique instances
  • Must be registered before use

Classes are namespace-scoped: a class_id only has meaning within its namespace. The same numeric ID in a different namespace is a different class. Once registered, a class ID cannot be reused for a different definition. This keeps identity stable for audit and replay.

Classes can optionally carry behavior constraints that are enforced at operation validation time. These constraints are how you encode “real world” limits into deterministic rules:

  • Balance scale (fixed-point quantization for balances)
  • Minimum and maximum balance floors/ceilings
  • Maximum stack size for fungible stacks
  • Maximum stacks per container for a class
  • Negative-balance capability (explicitly allowed or not)

Shapes define spatial footprints. They allow the runtime to enforce collision rules deterministically, which is essential for correctness.

  • Width and height in grid cells
  • Associated with a class and optional variant key
  • Required for grid container placement

Variants are expressed with stack_key. The (class_id, stack_key) pair defines fungible identity and determines which shape (if any) is required for placement. This lets you model variants (size, tier, condition) without multiplying class IDs.

Compatibility recap:

  • Balance: Fungible quantities only (fixed-point scale; no positions)
  • Slots: Instances only (one instance per slot)
  • Grid: Fungible stacks or instances (shapes required for multi-cell placement)
  • Continuous 1D/2D: Instances only (fixed-point coordinates; shapes/spans required)
{
  "op": "RegisterClass",
  "args": {
    "request": {
      "class_id": 200,
      "flags": 2,
      "name": "Sample Tube"
    }
  }
}

How it fits into the system

Validation and dispatch

Container kind is part of the operation contract. During L2 validation, the runtime checks container kind, class registration, and shape requirements before any mutation. Preflight uses the same checks, so a successful preflight implies the commit will pass if the world has not changed.

Examples of how this plays out:

  • AddFungible on balance increments a scalar balance.
  • AddFungible on grid requires shape registration and collision checks.
  • PlaceInSlot on balance is rejected (wrong container kind).

Registry and shapes

Class and shape registration live in the namespace registry. Grid and continuous placements consult the registry for footprints or spans, so placement is deterministic and replay-safe instead of ad hoc.

Read projections and query surfaces

Read projections keep container-specific indexes (balances by class/key, slot occupancy, grid anchors). Read responses return typed payloads by container kind, so “container contents” queries always match the geometry the container enforces.

See also