usd-stage-runner is an experimental C++ runtime that opens an OpenUSD Stage,
derives a transient Runtime World from its prims, and advances that world in a
bounded real-time update loop. The project is being delivered as small vertical
slices; physics, character control, and first- and third-person camera following
with collision avoidance are implemented.
The project is a lightweight runtime orchestration layer, not the owner of a
physics implementation. Reusable physics contracts and the Jolt backend are
consumed from installed usd-physics-plugins packages; Stage Runner retains
Runtime World, fixed-step scheduling, gameplay policy, Stage-specific import,
host lifecycle, and discardable USD synchronization. Installed physicsUsd
supports standard Box declarations by default through pinned Windows/Linux
packages; broader support remains in progress. See the
physics repository boundary.
The intended architecture and the distinction between implemented and planned behavior are documented in docs/README.md.
From the repository root in PowerShell, use an OpenUSD 26.08 install with
usdview and Python 3.13, matching strata.lock. On Windows,
python must be on PATH because the OpenUSD usdview.cmd launcher calls it.
Replace the example install path with your own:
$usdRoot = 'C:\path\to\openusd-with-usdview'
$env:CMAKE_PREFIX_PATH = 'C:\path\to\jolt-install'
ost runtime pull cy2026 --profile usd --from-usd $usdRoot
ost runtime pull cy2026 --profile lookdev --from-usd $usdRoot
ost library pull
ost build --intent physics-usd
$fixture = (Resolve-Path .\tests\fixtures\standard_character_follow_camera.usda).Path
ost plugin view plugins/runnerSchema $fixture --profile lookdevIf the runtimes and bundle are already built, only the last two lines are
needed. Choose Stage Runner > Play, then use WASD to move PlayerCube.
The fixture path is absolute because ost plugin view resolves relative fixture
paths from the bundle directory.
The pinned Jolt-enabled physics artifacts require their matching Jolt SDK on
CMAKE_PREFIX_PATH. WASD and the arrow keys move the cube; Space jumps. Play
selects the scene's third-person rig when usdview is using its free
camera. The camera then follows the cube. If you have already chosen another
scene camera, Play keeps that choice.
runtimeCore, with an injectable frame clock, bounded fixed-step accumulator, a host-facing play-session controller for play, pause, stop, single-step, and reset, a prim-indexed component registry, Runtime World, runtime transforms, and a dirty synchronization queue;inputCore, with named action state and backend-neutral movement intent;- external
physicsCore, with typed backend-neutral resource handles, descriptors for boxes, bodies, and fixed constraints, fixed-step commands, changed-body extraction contracts, character ground and collision-segment query extensions, and prim/body runtime synchronization; characterCore, with backend-neutral character intent and controller state, walkable-ground and slope evaluation, desired motion, facing, jump-edge handling, and deterministic tests against a physics test double;cameraCore, with prim-indexed target and optional anchor identities, free, first-person, third-person, and orbit modes, deterministic target following, optional third-person collision probes, live pose state, exponential smoothing, and dirty Runtime World translation updates without OpenUSD, Jolt, or a renderer;vehicleCore, with normalized throttle, brake, steering, and handbrake intent; explicit chassis identity; independently composed steering, powertrain, service-brake, and handbrake configuration; and deterministic per-wheel command distribution that does not assume four wheels;- external
physicsJolt, which owns Jolt initialization and resource lifetime, creates box shapes and static or dynamic bodies, advances fixed simulation steps, extracts changed body state, and implements character ground shape casts and camera collision ray casts without exposing Jolt types publicly; runnerSchema, a codeless OpenUSD plugin defining the single-applyRunnerPhysicsBodyAPI,RunnerColliderAPI,RunnerCharacterAPI, andRunnerCameraRigAPIdeclaration contracts;stageRuntime, a reusable OpenUSD-facing play session that owns the prim/body bridge, imports the Runtime World and physics, character, and camera systems, owns fixed-step execution and reset/rebuild semantics, and incrementally synchronizes dirty transforms into a discardable anonymous runtime layer for any host without changing persistent authored layers;inputSdl, which maps WASD, arrow keys, and the first gamepad's left stick tomove.xandmove.y, and maps Space or the gamepad south button tojump, without exposing SDL types to core consumers;stage_runner, a thin standalone adapter that opens a Stage, selects Jolt and SDL adapters, polls input, and drives the sharedstageRuntimesession for an explicit frame bound;usdviewStageRunner, a Python usdview menu and timer adapter backed by a native binding to the sameStageSession, with play, pause, stop, single-step, and reset controls and bundled Runner schema registration;- minimal transform, falling-cube, character-import, runnable walk-and-jump, and obstructed first-/third-person camera-follow USDA fixtures plus dependency-free unit tests; and
- dual build paths through plain CMake and OpenStrata.
The character-control, camera-rig, and host-integration milestones are implemented end to end. The physics core and Jolt backend have been extracted, their pinned Windows/Linux packages are publicly available, and Stage Runner's package migration has passed hosted verification. Standard Box import has passed Windows/Linux parity against the published parser pins. Vehicle physics application remains paused until standard physics delivery and shared MMD/VRM validation advance. Behavior and OpenExec integration are later slices.
OpenStrata supplies the pinned
OpenUSD runtime and compiler environment. CI uses ost 0.23.1; use that
version locally when reproducing its checks:
$runtimeArtifact = 'sha256:ebb0c7da509ee14ada19ee5b461de6996aad0024b5c9640f12dde76912e849b5'
ost artifact pull 'oci://ghcr.io/animu-sphere/openstrata-runtime-cy2026-usd@sha256:d3ff79a6f330558c3b9a427a927d340fe3dcab1fe89107faa0ea9f66a104b7bf' `
--expect-artifact $runtimeArtifact --require-kind runtime
ost runtime pull cy2026 --profile usd --from-artifact $runtimeArtifact --force
$libraries = (ost library pull --json | ConvertFrom-Json).data.libraries
$externalPrefixes = @($libraries | ForEach-Object { $_.prefix })
$env:CMAKE_PREFIX_PATH = ($externalPrefixes + $env:CMAKE_PREFIX_PATH) -join ';'
ost build
ost testThe migration pins both the physics artifact content digests and immutable
public OCI source URIs. After materializing the matching pinned runtime as
shown above, ost library pull can populate a fresh OpenStrata cache without
repository-local package builds.
The reusable core can also be built and tested as an isolated OpenStrata library member:
ost library build libs\runtimeCore
ost library test libs\runtimeCoreTo run the staged executable directly, first enter the runtime-activated shell:
ost devshell cy2026 --profile usdThen run the deterministic smoke path inside that shell:
.\apps\stage_runner\bin\stage_runner.exe tests\fixtures\standard_falling_cube.usda --frames 180 --deterministicA C++17 compiler is sufficient for runtimeCore. The complete repository build
requires installed physicsCore and physicsJolt packages, plus physicsUsd
when building Stage integration with its default standard importer. Point
CMAKE_PREFIX_PATH at those packages and OpenUSD; a Jolt-enabled
physicsJolt package also requires its Jolt SDK dependency to be discoverable:
cmake --preset dev -DCMAKE_PREFIX_PATH="C:\path\to\usd-physics-plugins;C:\path\to\jolt;C:\path\to\openusd"
cmake --build --preset dev
ctest --preset devStandard Box import is enabled by default. Obtain the published parser with
ost library pull, or install the sibling repository with
USDPHYSICS_BUILD_USD=ON, then add its prefix and the same OpenUSD SDK to
CMAKE_PREFIX_PATH. Try
tests/fixtures/standard_falling_cube.usda or
tests/fixtures/standard_character_follow_camera.usda with either host.
See the supported subset.
ost build --intent physics-usd and ost test --intent physics-usd require the
same standard import and stage the usdview adapter. The intent requires OpenUSD
and a Jolt-enabled installed
backend. ost library pull now resolves published Windows/Linux physicsUsd
packages through exact content and OCI digests. The CI gate consumes those
binaries through default-enabled plain CMake, an explicit compatibility-only
build, and this intent. Set USD_STAGE_RUNNER_ENABLE_PHYSICS_USD=OFF for a
compatibility-only build without the parser; standard bodies then produce an
enable-option diagnostic. See the public-pin report.
The two core/backend physics packages are required at configure time. Without OpenUSD, the
host still compiles but reports that Stage loading is unavailable; the
backend-neutral unit tests remain buildable. Set
USD_STAGE_RUNNER_REQUIRE_OPENUSD=ON when a missing SDK should be a configure
error. Interactive input is enabled when CMake can find SDL3::SDL3 or
SDL2::SDL2; set USD_STAGE_RUNNER_REQUIRE_SDL=ON to require a real SDL-backed
demo build. The OpenStrata usd profile does not currently bundle SDL, so pass
an SDL package through CMAKE_PREFIX_PATH for interactive builds. Set
USD_STAGE_RUNNER_REQUIRE_JOLT=ON to reject an installed physicsJolt package
whose exported metadata reports that the backend was built without Jolt
support. Otherwise that package remains usable for honest
unavailable-backend behavior.
The usdview adapter is built when OpenUSD includes Python support. Add the
generated package parent to PYTHONPATH and the package directory containing
plugInfo.json to PXR_PLUGINPATH_NAME:
$env:PYTHONPATH = "$PWD\build\cy2026-windows-x86_64-py313-usd\plugins\usdviewStageRunner\python;$env:PYTHONPATH"
$env:PXR_PLUGINPATH_NAME = "$PWD\build\cy2026-windows-x86_64-py313-usd\plugins\usdviewStageRunner\python\usdviewStageRunner;$env:PXR_PLUGINPATH_NAME"
usdview tests\fixtures\standard_character_follow_camera.usdaThe Stage Runner menu exposes Play, Pause, Stop, Single Step, and Reset.
After Play, click the viewport and use WASD or the arrow keys to move
/World/PlayerCube; Space requests a jump in a Jolt-enabled character Stage.
Stop and Reset discard the plugin-owned anonymous runtime layer; they do not
change persistent authored layers. See the
plugin README for layout and runtime
defaults.
The plugin-view build intent stages that same usdview package and native
StageSession binding into the runnerSchema bundle. OpenStrata then supplies
the bundle's Python, plugin-discovery, and loader paths. Use the
quick-start command above to launch it.
The plugin-view intent stages the adapter inside runnerSchema. OpenStrata
0.23.0 added a first-class usdview-plugin bundle for --with composition,
but this repository's pinned usd profile does not promise usdview. Moving
the adapter to its own bundle also requires a runtime artifact with the
usdview capability and corresponding CI coverage.
standard_third_person_camera.usda can be opened the same way. The selected
OpenStrata runtime must contain usdview, and the installed physicsJolt package must have
backend support to execute physics declarations; otherwise the shared adapter
reports the same unavailable-backend error as stage_runner and ordinary
usdview.
stage_runner <scene.usd[a|c]> [--frames N] [--fixed-dt SECONDS]
[--max-fixed-steps N] [--deterministic]
[--move-x VALUE] [--move-y VALUE] [--jump]
The default loop runs 300 frames at a 60 Hz target. --deterministic injects
one fixed interval per frame and does not sleep, making integration tests fast
and repeatable. --move-x and --move-y accept normalized values from -1 to 1,
and --jump holds the jump action for deterministic adapter-to-Stage tests.
All three require --deterministic. Interactive runs use SDL keyboard and
gamepad input.
The current compatibility importer expects physics prims to apply both
RunnerPhysicsBodyAPI and RunnerColliderAPI. The target authored model uses
standard UsdPhysics; existing Runner declarations remain documented here
until that migration is implemented.
runner:physics:motionType accepts static or dynamic, mass is authored with
runner:physics:mass, and the initial collider contract uses
runner:physics:shape = "box" plus positive local-space
runner:physics:halfExtents. Ordered scale ops multiply those extents. A Stage
with physics declarations must be Y-up with metersPerUnit = 1. Physics prims
require one translate op followed only by scale ops, plus identity ancestor
transforms unless they set resetXformStack. A build with both OpenUSD and Jolt
is required to simulate the declarations. Authored runner:physics:*
attributes without their owning API schema are rejected as legacy data.
Character prims additionally apply RunnerCharacterAPI to the same dynamic
physics prim. runner:character:groundProbeDistance,
runner:character:maximumSlopeAngleRadians, and
runner:character:jumpSpeed configure the prim-indexed runtime controller.
Character attributes without RunnerCharacterAPI, characters without both
physics APIs, and static character bodies are rejected.
Camera prims apply RunnerCameraRigAPI. runner:camera:target and the optional
runner:camera:anchor are prim relationships; non-free modes require exactly
one target. Mode, offset, distance, pitch, yaw, damping, collision enablement,
and collision clearance are read into a prim-indexed camera rig. The importer
rejects non-camera application sites,
unresolved or non-xformable references, invalid values, and authored camera
properties without their owning API schema.
Camera declarations currently require a Y-up Stage. Camera, target, and anchor
translations must already be representable in world space by their imported
local RuntimeTransform; non-identity ancestor transforms require
resetXformStack until composed camera transforms are supported. Rig Camera
prims accept an empty transform stack or one translate op optionally followed
by the reserved double-precision xformOp:orient:runnerCamera; other authored
transform ops are rejected so the runtime pose is not composed with an unknown
rotation. A rig may not target or anchor itself. At each fixed step, imported
rigs evaluate after physics extraction. Changed camera poses use the shared
dirty queue to update translation and the reserved orientation only.
Collision-enabled third-person rigs use the optional backend-neutral segment
query, ignore a bound target body, shorten the desired camera distance by the
authored clearance, and smooth the collision-adjusted pose.
Project code is licensed under Apache-2.0.