When a Simulation Result Can Count as Evidence
The brief determines whether a 664 mm slide needs a repair
Watch the video explainer
5:30Connect a simulation result to the right saved scene and the current assignment.
Loads YouTube when you play.
Before I use a simulation result to accept a 3D delivery, I need to know that it belongs to the saved scene under review and answers the task I assigned. A small movement from yesterday’s model cannot establish today’s result. A large movement may be exactly what a comparison was meant to show.
I added a simulation worker to Scene Acceptance, my tool for checking 3D deliveries, to make that handoff inspectable. The worker runs a supported block-on-ramp model and returns the scene’s identity and a trace of the block’s movement. The harness checks that evidence before applying the brief’s acceptance rule.
One assignment shows why the rule matters. In the earlier incline experiment, the contract, the file of targets and tolerances the checks read, required the block to stay within 10 mm. This assignment asked the producer, the agent building the scene, to preserve a supplied model, set its mass to 1.25 kg and deliver three friction variants. It did not ask every variant to stay put. In the low-friction variant, the block moved about 664 mm down the incline, well beyond that 10 mm limit, which here was only an advisory comparison, but the delivery still met the production brief.
Under the earlier contract, the same kind of measurement was a rejection. The two contracts ask for different work, and I want the brief, rather than the number on its own, to decide what a measurement means. The slide is evidence about the low-friction variant, and under this brief it calls for no repair.
Accepting those requested variants does not mean the movement is unimportant. Someone choosing a setup that must keep the block in place would need to consider the displacement and the assumed contact properties. Keeping the result visible lets that later decision use the comparison, instead of changing a correctly delivered variant just to remove an advisory finding.
The same comparison can serve different tasks
The brief asked for friction values of 0.22, 0.42 and 0.68 on the supplied two-box model, a fixed ramp and one block. Three attempts delivered nine scenes. The selected checks confirmed the requested parameters and that the base, the supplied model, was preserved; displacement was measured as an advisory comparison, reported beside the decision without deciding it.
The evaluating assistant ran all nine scenes at 1 ms and 0.5 ms timesteps, and the 18 traces matched direct execution through the same adapter, the code that converts the supported scene into a simulator model. At friction 0.22, the maximum displacement was 664.272 mm at 1 ms and 663.941 mm at 0.5 ms. All three attempts supplied the same physics inputs after parsing and gave the same values at each timestep. Repeating those inputs checks only the handoff.
An extra-object control, a constructed copy with one added object, tested another boundary. The selected physics-configuration rules passed, but the worker could not assess the copy and returned unknown because it only covers the two-box structure, and a separate base-preservation check rejected the copy under this brief. Without that requirement, an extra object would have called for a more capable evaluator rather than a repair.
Tie the worker to the scene being accepted
The adapter accepts only the fixed ramp and one rigid block, with limited mass and friction variations. It reads that supported structure from the OpenUSD (USD) scene file and writes an explicit model for MuJoCo, an open-source physics simulator. This is a small CPU experiment using MuJoCo’s Python bindings. I kept the model unchanged from the earlier experiment so I could tell whether the worker handoff had altered the measurement.
The harness admits the saved USD, copies it into a temporary job directory and launches fixed, installed worker code in a separate Python process. The job records the input hash, timestep, duration, and the worker and adapter source hashes. The candidate, the delivered scene under review, cannot choose an executable or submit Python to run.
Review caught that the worker and adapter hashes were being recorded without being verified, and the check was added. Release 0.5 verifies the installed worker, adapter and fixed two-box profile before and after execution, then checks the identities the worker returns in the parent process, the harness process that launched it. A missing or changed identity cannot supply accepted evidence.
The worker returns its identities and runtime versions, simulated times, block positions, displacement samples and the maximum displacement. The parent first checks that the record belongs to the current job. A low value from yesterday’s scene cannot pass today’s requirement just because it is below the limit.
It then checks trace length, time spacing and finite values, recomputes the displacement along the slope from the saved positions and the ramp angle, and takes its maximum absolute value. If the reported scalar disagrees with that trajectory, the result is rejected as unusable evidence, an evaluation error.
These checks catch stale or inconsistent output from the trusted worker. They would not catch a worker that invented a self-consistent trajectory; that’s why I keep the model and the CSV trace for someone to inspect and recompute.
Check the handoff against the earlier experiment
Before using the new producer variants, I kept the original 1 kg block and friction values of 0.15 and 0.65 as regression controls. The earlier experiment’s standalone runner, direct worker execution and integrated harness reproduced these maxima in the recorded environment:
Scroll sideways for more columns.
| Assumed friction | 1 ms step | 0.5 ms step | Original required limit: 10 mm |
|---|---|---|---|
| 0.65 | 1.064 mm | 1.045 mm | Pass. |
| 0.15 | 987.227 mm | 986.730 mm | Fail. |
That agreement checks the integration. All three paths share the adapter and simulator, so it says nothing about whether the contact model is right. The earlier article’s trace analysis works through the ideal calculation, which predicts no movement at 0.65. A later solver study traced much of the small high-friction creep to solver settings: in five-second runs at a 2 ms step, drift during the last second fell from 0.873 mm to under 0.01 mm with impratio=100 or ten noslip iterations. Both changes affect how the contact solver handles the constraint.
The worker’s pack of checks also keeps assumed parameters visible. When a control requires measured friction, the passing high-friction trajectory returns insufficient evidence. This prototype has no workflow for admitting a measured friction record, and a clean run doesn’t turn an assumed friction into a measured one.
A worker failure needs a different response
The caller, the application that invokes the harness, sets a wall-clock deadline. The worker used for these runs also stops at 10,000 simulation steps, and the parent caps evidence reading at 4 MiB, so the caller can stop waiting on an unusable job. It runs as the same operating-system user, without a hard memory limit or network sandbox. The development package, an unreleased 0.7 snapshot of the harness, adds a separate supervised execution option with process-tree CPU, memory and time budgets, while filesystem and network isolation still need an operating-system sandbox.
My coding assistant introduced deliberately interrupted and malformed runs, wrong identities and inconsistent trajectories to test the handoff. These controls show how it responds; they do not measure how often real runs fail.
Scroll sideways for more columns.
| Observation | Result under the original stay-within-10-mm contract | Caller action |
|---|---|---|
| Complete, matching trajectory exceeds 10 mm | REJECT | Investigate the model or requirement; do not silently change contact assumptions. |
| Complete trajectory stays within 10 mm | ACCEPT_FOR_USE | Retain the model assumptions with the simulated result. |
| Timeout, crash, malformed or oversized output | EVALUATION_ERROR | Fix or retry the execution. There is no usable behavior measurement. |
| Wrong input/job identity or inconsistent scalar | EVALUATION_ERROR | Investigate the evidence handoff. |
| Scene outside the two-box adapter | INSUFFICIENT_EVIDENCE | Use a suitable evaluator. |
| Measured friction required but absent | INSUFFICIENT_EVIDENCE | No supported workflow yet; the result stays insufficient. |
Reproduce the worker and its controls
The nine-scene study ran on a development build before release 0.5. The worker remains included in release 0.6. The development package lists it in the example catalog (check-3d-packs --include-examples); a contract that names it still loads it for replay.
Scene Acceptance 0.5 on GitHub includes the restricted worker and its identity checks. The v0.1 companion reproduces the original fixed-model experiment and fault controls, with their inputs, traces and reports. It does not include the identity-verification fix or the later nine-scene study, and its receipts, the records tying each run to its inputs, predate that fix.
After the companion installation, the matrix runs the worker cases and compares them with direct execution:
python evaluation/followups-v1/run_matrix.py /tmp/worker-replay-01
python -m pytest tests/test_followups.py -q
The case folders keep report.json; completed simulations also have trace.csv, model.xml and, where applicable, a direct-run result. The test source shows each injected fault. The original experiment remains separately replayable with its pinned article dependencies.
My coding assistant produced the code and constructed controls under my direction.
The recorded runs use Python 3.12.14, OpenUSD 25.11, MuJoCo 3.6.0 and NumPy 2.5.3 on macOS arm64. The original configuration comparison uses NVIDIA USD Validation 1.20.0, NVIDIA’s rule library for checking OpenUSD files; the new simulation result comes from MuJoCo and the bounded adapter.
The worker’s identity checks tie a result to the scene under review; the brief then decides what it means. The 664 mm result is the case I would keep visible when adding a repair loop that sends findings back to the producer. The producer delivered the requested low-friction variant. A loop that kept changing it until the advisory displacement turned green would have made the submission worse for the task we gave it.
Disclaimer: The views and opinions expressed in this account are those of my own and do not represent those of my employer, NVIDIA.