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
| Affordance | Meaning | Typical part |
|---|---|---|
graspable | The gripper can close on this part stably. | handle, knob, body |
rotatable | Rotates about a revolute joint when engaged. | knob, lever, valve |
slidable | Translates along a prismatic joint. | drawer face, sliding lid |
pressable | Yields under perpendicular force; no grasp needed. | button, key, pad |
openable | Hinged; rotates open to expose interior. | door, panel, flap |
liftable | Can be lifted away after engagement. | cap, lid, removable cover |
insertable | Can be inserted into a mating socket. | plug, peg, key |
flippable | Two stable states (on/off). | switch, toggle |
placeable | Has a flat / well-defined seat surface. | cup base, block face |
stackable | Can receive another object on top. | cube top, plate, lid |
drawable | Slides 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_skilldecorator). - 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.