Original series Accepting Agent-Generated 3D Part 5 of 5
Evaluating Physics Configuration and Simulated Behavior
Both blocks passed the physics configuration checks and only one met the task. I want the report to say what each pass covered and what it assumed
Watch the video explainer
5:17Separate valid physics settings from the behavior the simulated task actually requires.
Loads YouTube when you play.
A 3D scene prepared for simulation includes physical properties such as mass, collision geometry and friction. Checking those settings helps establish whether the model is configured as required. To decide whether a simulated task worked, I also need to run the model, measure what happened and compare it with the task’s limit.
I used a block on a ramp to examine that distinction. The task was to keep its movement within 10 mm over one simulated second. Comparing two assumed friction values let me inspect the configuration checks and the simulated outcome separately, including a small movement inside the passing limit.
The ramp is a constructed evaluator test. A separate press-cell example compares a general run of Scene Acceptance, my 3D checking tool, with an existing simulation report from the same saved file. That comparison did not rerun the simulation. Both examples help me decide what evidence to request before treating a physics-related pass as acceptance of the task.
The ramp, the block and the task
After the animated assembly, I wanted to separate valid physics configuration from evidence that a simulated task succeeded. I kept this experiment to a block and ramp, where I could inspect every input and the whole trace. My coding assistant constructed the evaluator controls and their runner under my direction. The protocol, inputs and expected decisions were fixed before execution.
Scroll sideways for more columns.
| Input | Value used in the experiment |
|---|---|
| Block | 0.2 × 0.2 × 0.1 m; authored mass 1 kg. |
| Fixed ramp | 20-degree incline; block initially aligned with it. |
| Initial surface gap | 0.1 mm. |
| Gravity | 9.81 m/s². |
| Contact assumptions | Equal static and dynamic friction of 0.15 or 0.65; restitution zero. |
| Task | Maximum absolute displacement along the slope ≤ 0.010 m over one second. |
| Timestep comparison | 1 ms and 0.5 ms with the other solver settings held fixed. |
Before any simulation, the harness applies three rules from NVIDIA USD Validation, NVIDIA’s rule library for OpenUSD (USD) scene files, covering mass, rigid bodies and colliders. The protocol, runner and three USD fixtures are on GitHub. The third fixture has deliberately negative mass, to check that the configuration stage can reject an invalid input before any behavior result is considered.
The 10 mm limit is an illustrative requirement, chosen before observing the result. The task metric is the largest displacement along the slope at any step of the run. The runner projects each change in position onto the downhill direction and compares the largest magnitude with the limit, so a block that slid and came back would still fail.
A separate runner simulates the supported ramp and block in MuJoCo, an open-source physics simulator. Its conversion only covers this small model.
Configuration passed; the task separated the cases
The selected configuration checks accepted both positive-mass scenes. Applying the same displacement limit to their simulated motion separated them:
Scroll sideways for more columns.
| Friction assumption | Selected configuration rules | Maximum displacement at 1 ms | Simulated task |
|---|---|---|---|
| 0.15 | Pass | 987.23 mm | Fail: exceeds 10 mm. |
| 0.65 | Pass | 1.06 mm | Pass: within 10 mm. |
| Negative-mass control | Fail | Not simulated | Configuration rejected. |
The negative-mass rejection shows these rules go beyond syntax, but none of the three measures how far the block moves or where its friction value came from.
The higher-friction block passed the 10 mm task, but it did not stay still. Its saved simulation trace showed about 1.1 mm of movement on the 20-degree incline. An ideal rigid-friction calculation predicted zero.
I kept the small displacement visible and reran both cases at half the timestep. Neither run supplied evidence for the friction coefficients, which were assumptions.
A short mechanics calculation gives a diagnostic comparison. An ideal rigid Coulomb model gives the lower-friction block downhill acceleration of approximately g × (sin(20°) − 0.15 × cos(20°)), or 1.97 m/s². Starting from rest, that predicts about 986.23 mm over one second, against the simulated 987 mm. The larger coefficient exceeds tan(20°), approximately 0.364, so that idealized model can hold the block at rest.
Halving the timestep produced approximately 986.73 mm and 1.05 mm. Both decisions stayed the same. The final-displacement differences were about 0.50 mm for low friction and 0.019 mm for high friction, with no simulator warnings recorded. Both coefficients sit far from the 0.364 boundary, so this says nothing about a case near it.
The one-second experiment could not tell me where the high-friction block’s millimetre came from: initial settling, the numerical contact model or integration. A twelve-run follow-up separated part of it by keeping the geometry, mass, gravity and friction fixed and changing only the solver settings over five seconds. With a 2 ms timestep and the experiment’s original solver settings, the block had moved 4.516 mm after five seconds and was still creeping, 0.873 mm in the last second alone. Changing the contact solver settings reduced that drift: with impratio=100 the last-second drift fell to 0.00873 mm, and with ten noslip iterations to 0.000116 mm. The 1 ms runs showed the same pattern, and no run reported a solver warning.
Friction never changed across those runs, so when the last-second creep shrank a hundredfold or more as only the solver settings changed, I read much of that creep as coming from how the solver handled the contact. It also means the original 10 mm pass is a pass over one second: under the original settings the block had not settled within five seconds, and the final displacements still depend on the timestep. I would keep the solver settings and the observation window with the result. MuJoCo’s computation documentation describes the contact and integration model behind those differences.
The same gap in a press-shop demo
A later check found the same distinction in an articulated press cell. A saved transfer-cell demo passed all 24 selected general rules, while an earlier PhysX run of the exact same file had recorded 51.6 mm of wrist tracking error against a 10 mm requirement. PhysX is NVIDIA’s physics simulation engine, and tracking error here is how far the simulated wrist strayed from its target motion.
That comparison came when I asked for the harness to be run against the saved Astra demos, scenes built earlier with Astra, an AI agent. The harness ran its older general policy, a set of delivery rules with no task targets, and the 24 passes included mesh rules with no applicable subjects. The PhysX run behind the 51.6 mm figure was an earlier five-second run at a 2 ms timestep. It also used placeholder masses and drive settings, so the 51.6 mm describes that model with those placeholder values.
Scroll sideways for more columns.
| Evidence for the same saved source | Result | What that result covers |
|---|---|---|
| Older general harness policy | 24 rules returned PASS; empty mesh coverage was included in that count. | No issues reported under this policy. The scene uses 64 cubes, so the polygon-mesh rules had nothing to assess. |
| Retained PhysX run, 2 ms timestep | Maximum tracking error 51.628 mm; limit 10 mm. | The simulated wrist failed the chosen tracking requirement. |
| Scene comparison | The saved files matched. | Both reports refer to the same scene revision. |
The comparison record and structural report preserve the older 24-rule policy separately from the newer 27-rule profile in the core article, which reports how many subjects each rule could assess. The retained simulation report supplies the tracking result; the audit compared existing evidence and did not rerun the simulation. The generic contract, the file of targets and tolerances the checks read, had no tracking target and never read the trajectory, so the harness had no way to see this failure.
A tracking check needs the target motion, a tolerance and a matching trace, plus an evaluator that understands the mechanism. The two-box MuJoCo adapter cannot assess an articulated press cell. The harness’s unreleased development package has an engine bridge that the caller, the application invoking the harness, runs to import selected results from a native engine report. It has not been tested against a real install of PhysX or of SimReady, NVIDIA’s requirement sets for simulation-ready assets, so I can’t yet say what it would have reported here.
What the assembly would need for a physics task
A read-only inspection of the submitted crank-slider USD found no authored rigid-body, collision, mass or physics-material APIs, and no joints or physics scene, which fits its original visualization brief. If I changed the request to a dynamics task, such as whether the slider completes its stroke under a specified load, I would first define the load, drive and limit. Then I would require the body, joint, collision and contact parameters described in USD’s physics schemas, each with supplied evidence or an approved assumption.
Reproduce the ramp experiment
The commands below replay the standalone v0.3 experiment. Releases 0.5 and 0.6 include its restricted CPU worker, a separate process that runs this ramp model for the harness, as a selectable check. The development package lists it as an example pack; the worker follow-up explains that handoff.
Start with the core installation steps. From the extracted project directory, add the pinned article dependencies (OpenUSD 25.11, NVIDIA USD Validation 1.20.0, MuJoCo 3.6.0 and NumPy 2.5.3) and evaluate the low-friction fixture with the harness’s check-3d command:
python -m pip install -r requirements-articles.txt
check-3d \
--bundle-root article-evidence/physics-experiment/fixtures/low_friction \
--contract contract.json --candidate scene.usda \
--out /tmp/physics-config-01
That returns ACCEPT_FOR_USE, the verdict when every required check passes, with exit code 0. Changing the fixture folder to negative_mass and choosing a new output directory returns REJECT, the verdict when a required check fails, with exit code 2. Each evaluation writes the common result.json and report.html.
The fixture’s full contract pins the nvidia.asset-validator pack to 1.0.0. That pack, a reusable group of checks, wraps NVIDIA USD Validation, and the contract’s one check selects these upstream rules:
{
"id": "physics.configuration",
"pack": "nvidia.asset-validator",
"check": "rules",
"required": true,
"parameters": {
"rules": ["MassChecker", "RigidBodyChecker", "ColliderChecker"],
"warnings_as_failures": false
}
}
This is an entry inside checks, not a complete contract. The pack’s adapter runs the named rules from NVIDIA USD Validation 1.20.0 and keeps their issues, severity and locations without invoking a fixer; warnings stay in the report without failing the check.
The runner simulates the two positive-mass scenes in MuJoCo, an open-source physics simulator, advancing each in fixed timesteps. A small adapter reads the frozen USD geometry, transforms, mass, gravity and bound physics material, then writes an explicit MuJoCo model of two boxes: a fixed ramp and a block with a free joint. It admits only this fixture structure and is not a general USD importer. The contact pair carries explicit friction values, so the result does not depend on implicit material-combination rules; MuJoCo’s contact documentation explains how pair parameters affect the model.
The behavior experiment verifies the frozen input hashes, runs the configuration checks and executes four one-second CPU simulations: two positive-mass scenes at two timesteps. The negative-mass control is not simulated.
python article-evidence/physics-experiment/run.py \
/tmp/incline-physics-01
Use a new output path; the runner refuses to overwrite an existing run and checks the input hashes again afterwards.
The top-level summary.json holds every decision, version, hash and timestep comparison. Each case folder holds the configuration result, the adapter readback of the parameters actually read from USD, and per-timestep model.xml, trace.csv and summary.json files. The higher-friction run’s summary contains these fields:
{
"dt_s": 0.001,
"samples": 1001,
"maximum_absolute_displacement_m": 0.0010640641191405945,
"behavior": "PASS",
"max_warning_count": 0
}
To adapt the project, keep the supplied experiment intact and create a separate protocol and fixture set; the runner checks frozen hashes, so replacing its input scene is deliberately not the extension mechanism. A new test needs a supported conversion, an independently chosen task limit, valid and invalid controls, and a record of which parameters were supplied, assumed or measured.
Keep the failed result until the model has a reason to change
For the ramp, the report I want says the configuration rules covered mass, rigid bodies and colliders but not displacement, the task pass held over one second at the recorded solver settings, and friction stayed assumed. For the press cell, it would say the 24 general passes never read the trajectory.
I would not let a producer, the agent that builds or repairs the scene, turn the failed 987 mm result into an accepted model of an existing contact by increasing friction until it passes. That coefficient describes the contact being modeled and needs evidence or an owner-approved assumption. Exploring a different surface is a legitimate new scenario, but it changes the question. For the original question, the failed result stays in the record until there is a supported reason to change the model.
Disclaimer: The views and opinions expressed in this account are those of my own and do not represent those of my employer, NVIDIA.