← Projects
Original series Accepting Agent-Generated 3D Part 5 of 5
  1. Building an Acceptance Harness for Agent-Generated 3D
  2. The 70 mm Gap Our Review Would Have Missed
  3. Checking Material Delivery in Agent-Generated 3D
  4. Checking Motion in Agent-Generated 3D
  5. Evaluating Physics Configuration and Simulated Behavior

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:17

Separate valid physics settings from the behavior the simulated task actually requires.

Read the article Watch on YouTube

Loads YouTube when you play.

In this article 6 sections

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.

InputValue used in the experiment
Block0.2 × 0.2 × 0.1 m; authored mass 1 kg.
Fixed ramp20-degree incline; block initially aligned with it.
Initial surface gap0.1 mm.
Gravity9.81 m/s².
Contact assumptionsEqual static and dynamic friction of 0.15 or 0.65; restitution zero.
TaskMaximum absolute displacement along the slope ≤ 0.010 m over one second.
Timestep comparison1 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 assumptionSelected configuration rulesMaximum displacement at 1 msSimulated task
0.15Pass987.23 mmFail: exceeds 10 mm.
0.65Pass1.06 mmPass: within 10 mm.
Negative-mass controlFailNot simulatedConfiguration 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.

The two blocks start in the same pose. The replay uses body positions and orientations from the retained 1 ms simulation traces, with the same scale and ramp crop. One stays within the 10 mm limit; the other slides about 987 mm. Both pass configuration checks. The separate behavior experiment finds the task failure. Playback is slowed four times, with holds; friction remains assumed.

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.

Measured MuJoCo displacement over one second. At assumed friction 0.15, the block moves approximately 987 millimetres. At 0.65, it moves about 1.1 millimetres. Both configurations pass the selected NVIDIA checks, but only the second stays within the 10 millimetre task limit. A separate magnified panel makes the small displacement visible.
Plotted from the recorded simulator traces, with separate scales for the full motion and the small displacement. Solid and dashed traces compare 1 ms and 0.5 ms timesteps. The 10 mm acceptance limit is an illustrative requirement, not a physical measurement.

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 sourceResultWhat that result covers
Older general harness policy24 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 timestepMaximum tracking error 51.628 mm; limit 10 mm.The simulated wrist failed the chosen tracking requirement.
Scene comparisonThe 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 installed NVIDIA pack checks three physics configuration rules. A separate CPU experiment uses OpenUSD and a restricted two-box adapter, MuJoCo simulation and a NumPy displacement comparison. Both friction cases pass the selected configuration rules; one simulated task fails and one passes. Friction remains assumed, leaving real-contact evidence unresolved.
Historical v0.3 experiment, with the 1 ms results shown. Gray identifies inputs and libraries; green identifies project evaluation. The dashed boundary is the separate CPU runner, not an installed runtime pack. The displacement plot above provides the traces behind these outcomes.

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.

← All projects