User Guide · Core Concepts · Affordances

Affordances

MetaFine uses a closed-set vocabulary of 11 affordances as the lingua franca between skills and assets. Compatibility is a lookup, not a heuristic — and that is what makes the platform composable at scale.

Why closed-set

Free-form affordance descriptors ("can be twisted", "liftable-via-knob") feel flexible but make composition impossible: every new skill must learn every prior label's idiosyncrasies. MetaFine takes the opposite stance — eleven affordances, fixed at the platform layer, expressive enough to cover fine-grained articulated manipulation, and small enough that the validator can reason about them.

If a new operation can't be expressed in the existing set, we add a new affordance at the platform level — every skill and every asset opts in explicitly. Twelve affordances tomorrow is fine; twelve thousand never.

The 11 affordances

AffordanceMeaningTypical part
graspableThe gripper can close on this part stably.handle, knob, body
rotatableRotates about a revolute joint when engaged.knob, lever, valve
slidableTranslates along a prismatic joint.drawer face, sliding lid
pressableYields under perpendicular force; no grasp needed.button, key, pad
openableHinged; rotates open to expose interior.door, panel, flap
liftableCan be lifted away after engagement.cap, lid, removable cover
insertableCan be inserted into a mating socket.plug, peg, key
flippableTwo stable states (on/off).switch, toggle
placeableHas a flat / well-defined seat surface.cup base, block face
stackableCan receive another object on top.cube top, plate, lid
drawableSlides outward from a containing housing.drawer, sliding tray

The contract

  • An asset declares which of its parts offer which affordances (in capabilities.json).
  • A skill declares which affordances it requires of its target part (in its @register_skill decorator).
  • The task-graph validator intersects the two: it confirms each stage's target part offers all the affordances the stage's skill requires.

A skill–asset mismatch never reaches the simulator; it fails at graph-validation time with a precise error pointing at the offending stage.

Declared by assets

Per-asset declaration lives in assets/<asset_id>/capabilities.json:

{
  "parts": {
    "cap":    ["graspable", "rotatable", "liftable"],
    "body":   ["graspable", "placeable"],
    "handle": ["graspable", "rotatable"]
  },
  "aliases": { "lid": "cap" }
}

Aliases let YAML authors write part: lid and have the resolver map it to the canonical part name. See capabilities.json reference for the full schema.

Required by skills

Each skill's requirements live next to its registration:

@register_skill(phase="interaction", requires=("graspable",))
def grasp_part(env, target): ...

@register_skill(phase="continuation", requires=("rotatable",))
def pure_rotate(env, target, *, angle_deg=90): ...

Compatibility lookup

Given an asset, MetaFine can enumerate applicable skills without any heuristics:

from core.skill_registry import applicable_skills
from utils.assets import load_capabilities

caps = load_capabilities("100221")         # { "cap": [...], "body": [...] }
for part, affs in caps["parts"].items():
    print(part, applicable_skills(affs))
# cap     ('grasp_part', 'pure_rotate', 'lift_lid', 'pure_lift', 'release_gripper', ...)
# body    ('grasp_part', 'place', 'release_gripper', ...)

This same function backs the task-graph validator and the AI-planner integration. The set is finite and computable, which is what makes evaluation comparable across heterogeneous assets.