Custom Models
Learn how to create your own dinosaur models for Mesozoic Labs.
Architecture Overview
Every species in Mesozoic Labs follows the same structure:
environments/<species>/
├── assets/
│ └── <species>.xml # MuJoCo MJCF model
├── envs/
│ └── <species>_env.py # Gymnasium environment (subclasses BaseDinoEnv)
├── scripts/
│ ├── train_sb3.py # Training script
│ └── view_model.py # Model viewer
└── tests/
└── test_<species>_env.py # Environment tests
You also need a stage manifest and TOML config files under configs/<species>/
for each training stage (see Stage manifest below).
Every implemented species must also have one entry in
configs/species_manifest.toml; the generated public catalog and its CI drift
check use that manifest as their source of truth.
MuJoCo XML Format
Dinosaur models are defined using MuJoCo's MJCF XML format. Here's a minimal skeleton:
<mujoco model="custom_dino">
<compiler angle="degree" coordinate="local" inertiafromgeom="true"/>
<option timestep="0.002" integrator="RK4"/>
<worldbody>
<light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>
<geom type="plane" size="50 50 0.1" rgba=".9 .9 .9 1"/>
<body name="torso" pos="0 0 1.5">
<joint type="free"/>
<geom type="capsule" size="0.3 0.5" mass="10"/>
<!-- Add limbs here -->
<body name="right_thigh" pos="0.2 0 -0.3">
<joint name="right_hip_pitch" type="hinge" axis="0 1 0" range="-60 60"/>
<geom type="capsule" fromto="0 0 0 0 0 -0.4" size="0.08" mass="2"/>
<body name="right_shin" pos="0 0 -0.4">
<joint name="right_knee" type="hinge" axis="0 1 0" range="-120 0"/>
<geom type="capsule" fromto="0 0 0 0 0 -0.35" size="0.06" mass="1.5"/>
</body>
</body>
<!-- Mirror for left side, add tail, neck, etc. -->
</body>
</worldbody>
<actuator>
<position name="right_hip_pitch" joint="right_hip_pitch" kp="100"/>
<position name="right_knee" joint="right_knee" kp="100"/>
<!-- One actuator per controllable joint -->
</actuator>
</mujoco>
Model Requirements
- Free joint on torso — The root body must have a
type="free"joint - Bipedal or quadrupedal stance — At least 2 legs with hip, knee, and ankle joints
- Joint limits — All hinge joints need
rangeattributes to prevent unnatural poses - Actuators — One position or motor actuator per controllable joint
- Appropriate mass distribution — Heavier torso, lighter extremities for stability
- Contact geoms — Feet need contact geometry for ground interaction
Existing Species as Reference
Use the generated specifications on the T-Rex,
Velociraptor, Brachiosaurus, and
Dibothrosuchus model pages for the current compiled
dimensions. Look at the corresponding MJCF
files under environments/<species>/assets/ for detailed examples.
Creating the Environment
Subclass BaseDinoEnv and implement species-specific reward components:
from environments.shared.base_env import BaseDinoEnv
class CustomDinoEnv(BaseDinoEnv):
def __init__(self, **kwargs):
super().__init__(
model_path="path/to/custom_dino.xml",
**kwargs
)
Register with Gymnasium by adding an entry in environments/__init__.py:
register(
id="MesozoicLabs/CustomDino-v0",
entry_point="environments.custom.envs.custom_env:CustomDinoEnv",
)
Stage Manifest
Create configs/<species>/stages.toml declaring the species' recipe graph:
one [[stages]] entry per node, in an order where every parent precedes its
children. The committed Velociraptor manifest is the three-node template:
schema = "mesozoic.stage-manifest/v2"
[[stages]] # quiet stance; gate reward-cleared by the statue (plan §4.8)
id = "stance"
config = "stage1_balance.toml"
legacy_number = 1
recipe = "stand"
deliverable = true
[[stages]]
id = "locomotion"
config = "stage2_locomotion.toml"
legacy_number = 2
warm_start_from = "stance"
recipe = "walk"
deliverable = true
[[stages]] # id stays "behavior"; the task name (strike) is the TOML's [stage] name
id = "behavior"
config = "stage3_strike.toml"
legacy_number = 3
warm_start_from = "locomotion"
recipe = "hunt"
deliverable = true
Per entry: id must match ^[a-z][a-z0-9_]*$ (stance, recovery,
locomotion and behavior are reserved, and a numbered reserved id must
declare its legacy_number); config is the stage TOML filename;
legacy_number is one of the three historical numbers, absent on an id-only
stage; warm_start_from names an earlier entry (a self or forward
reference is fatal); deliverable = true marks every node whose certified
checkpoint is a published policy (a v2 manifest with no deliverable is fatal);
and recipe is the behavior label (stand, walk, hunt). A species with a
recovery node uses the four-entry T-Rex layout, where recovery (recipe
stand, warm_start_from = "stance", no legacy number) sits between stance
and locomotion. See Behavior Recipes for how the
manifest drives training and publication.
Config Files
Create the TOML configs the manifest names, one per training stage, under
configs/<species>/:
[stage]
name = "balance"
description = "Learn to stand without falling"
[env]
forward_vel_weight = 0.0
alive_bonus = 1.0
energy_penalty_weight = 0.0005
max_episode_steps = 500
[ppo]
learning_rate = 3e-4
n_steps = 2048
batch_size = 64
[sac]
learning_rate = 3e-4
batch_size = 256
[curriculum]
timesteps = 1000000
min_avg_reward = 50.0
min_avg_episode_length = 400
required_consecutive = 3
Add the Species to the Public Catalog
Copy an existing [[species]] block in configs/species_manifest.toml
(schema version 2) and update its presentation metadata, environment entry
point, MJCF model path, notebook IDs, and result-summary paths. Add
[[species.success_metrics]] entries that state the actual success semantics
for every supported backend. Stage videos and curated result summaries are
optional, but any declared artifact must exist and include the required
backend and provenance metadata.
Per-deliverable success semantics
Every behavior the species publishes — each stage its configs/<species>/stages.toml
flags deliverable = true — needs a [[species.deliverable_metrics]] entry
stating what "success" means for that checkpoint:
[[species.deliverable_metrics]]
deliverable = "walk" # a recipe label, or a deliverable stage id
backends = ["stable-baselines3"]
key = "forward_velocity_gate"
label = "Gated forward velocity (reward_and_length/v1)"
definition = "The locomotion checkpoint clears the reward_and_length/v1 gate: ..."
deliverable resolves exactly as the training notebook's BEHAVIOR knob
does: a recipe label names its deepest deliverable in manifest order (on the
T-Rex, "stand" is the recovery node, so its stance node is addressed by the
id "stance"), and a stage id must be a deliverable. The generator fails
closed on an unknown deliverable or label, on a backend the species does not
train, and on two entries for the same stage and backend, and it requires a
stable-baselines3 entry for every deliverable the stage manifest declares.
A stance stage that is still gated by reward_and_length/v1 should say so in
its definition rather than claim certified stance quality.
Stage videos
[[species.stage_videos]] entries are keyed by stage id:
[[species.stage_videos]]
stage = "locomotion" # the stage id; a legacy integer (2) is an accepted alias
path = "website/static/videos/<species>_ppo_stage2_best.mp4"
algorithm = "PPO"
backend = "stable-baselines3"
model_revision_status = "historical"
verification_status = "unverified"
The id is the spelling that survives renumbering; a legacy integer resolves
through the stage manifest's legacy_number mapping, and naming one stage
both ways is rejected as a duplicate. A stage without a legacy number
(recovery) is reachable only by id.
Do not copy observation, action, or compiled-model dimensions into the manifest. The generator derives those values from the environment and MJCF, and reads stages and gates from the species TOML files.
From the repository root, regenerate the checked-in public data and README blocks, then verify that nothing is stale:
python -m environments.shared.species_catalog
python -m environments.shared.species_catalog --check
The check also confirms that every implemented species is represented in the manifest and that all declared configs, notebooks, videos, and summaries are valid.