Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,33 @@ jobs:
- name: Verify the packaged artifacts, composed (clean-install smoke)
shell: bash
run: python scripts/clean_install_smoke.py --skip-build
# The other half of "the artifact works": the clean-install smoke opens
# avatars through the four bundles, and nothing until 2026-08-30 had run a
# *tool* member out of an installed product. WORKSPACE.md §5 ships the
# motion profiles as product data so that this can pass, and the first run
# of it failed -- the tool lands at `tools/<member>/bin/` and looked for
# `<exe>/../share/...`, which is one directory too shallow inside the
# product's own prefix.
#
# `--product` rather than a fresh package: the archive this step is about
# is the one the previous steps just built and proved digest-reproducible,
# and re-packaging here would smoke a different artifact than the one the
# release stages.
- name: Verify the packaged BVH path from the product alone
shell: bash
run: |
set -euo pipefail
product="$(jq -r '.data.product.archive // empty' .ost-ci/package.json)"
if command -v cygpath > /dev/null; then product="$(cygpath -u "$product")"; fi
# `// empty` and this guard together, for the reason the staging step
# below has the same pair: a bare `jq -r` on a missing key prints the
# string "null", `dirname` turns that into ".", and the smoke would be
# pointed at the repository root and fail with a message about the
# wrong thing.
if [ -z "$product" ] || [ ! -f "$product" ]; then
echo "::error title=release packaging::workspace product archive is missing" ; exit 1
fi
python scripts/artifact_only_bvh_smoke.py --product "$(dirname "$product")"
- name: Verify the packaged VRMA reader independently
shell: bash
run: ost plugin test plugins/usdVrmaFileFormat --from-package --target ${{ matrix.platform }} --profile ${{ matrix.profile }} --up-to 5 --json
Expand Down
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,37 @@ Current schema contract version: **1**.

### Fixed

- **A tool member of the installed product could not find the product's own
data.** `motion_bvh_convert` derives its profile directory from its own
executable path, and its installed-prefix rule was
`<exe>/../share/usd-vrm-plugins/profiles/motion` — correct for a `cmake
--install` prefix and for a member archive unpacked on its own, and one
directory too shallow for the aggregate product, where `ost plugin product
install` lands a tool member at `<prefix>/tools/<member>/bin/` while the
product's data goes to `<prefix>/share/`. A converter that finds no profile
refuses every file it is given, so the whole BVH path was unusable from a
release artifact: the profiles shipped, byte-identical, to the directory
WORKSPACE.md §5 names, and the tool beside them looked somewhere else. The
locator now carries both installed layouts.

Both three-parent rules — the new one and the repository one beside it, which
had always been unguarded — are now offered **only when the executable really
is in a `tools/<member>/bin/`**. From a `cmake --install` prefix they would
otherwise climb two levels *above* it, which is where a sibling install of
this product puts its own `share/`, and the result is not a refusal but a
conversion reading another prefix's profile. That is the near-miss the
no-default-profile rule exists to prevent, arriving through the search path
instead of through a flag. Caught in review before either rule shipped, and
reproduced first: an executable at `a/b/prefix/bin/` with a profile only at
`a/share/…` converted instead of refusing.

**It was found by running it, and nothing else could have found it.** The
destination is stated in `openstrata.toml`, in the root `CMakeLists.txt`, in
WORKSPACE.md §5 and in `ProfileLocator.h`, all four agree, and one of them was
describing a different prefix. `ost` 0.22.3 supplied the staging in August and
report 36 §4 recorded in as many words that the staging was what had been
proven and not the run; this is the run, and it failed the first time.

- **A rotation too small to square came back un-normalised, in the two
conversions that had not been fixed.** `GfQuatf::GetLength()` squares in
float, and both `vrmAdapterVmc::ToCanonicalRotation` and
Expand Down Expand Up @@ -130,6 +161,29 @@ Current schema contract version: **1**.

### Added

- **An artifact-only smoke for the BVH path**
(`scripts/artifact_only_bvh_smoke.py`), which closes the v0.7.0 release
condition *both paths running from release artifacts alone, profiles
included* for the recorded half. It packages the aggregate product, runs
`ost plugin product verify`, installs it to a fresh prefix **outside** this
repository, and converts a real 17-second mocopi export there — 853 frames at
50 Hz through 22 bound joints — with no `--profile-dir`, no
`USDVRM_MOTION_PROFILE_PATH`, and nothing from this source tree on any search
path.

Two of its checks are the ones worth naming. Every shipped profile is compared
**byte for byte** against `profiles/motion/`, because the failure that shape
replaces was a *copy* that had stopped being the file
`scripts/check_motion_profiles.py` validates. And after the conversion
succeeds the installed profile is moved aside and the same command is re-run,
which must now refuse — so "the tool found a profile" cannot pass for "the
tool found the one this product ships".

It runs in `release.yml` beside the clean-install smoke, against the archive
that lane just proved digest-reproducible rather than a fresh package of its
own. That places it in the one workflow no PR event runs, which is a standing
caveat of that lane and not a new one.

- **`osc`, the OSC wire format once instead of once per adapter.** Packets,
bundles and their flattening, addresses, type tags, arguments, and a refusal
that names the byte and the address it refused at. It knows no address
Expand Down
43 changes: 33 additions & 10 deletions docs/architecture/WORKSPACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -702,9 +702,13 @@ smoke test of the BVH path impossible to pass.
--install` met it.** A packaged product did not: `ost` packaged a tool member
out of the `directories:` its descriptor declared, had no notion of a data-only
member, and the measured `motion_bvh` archive was exactly its two executables
and its descriptor. Unpacked and run, the converter refused a real capture and
named `<prefix>/share/usd-vrm-plugins/profiles/motion` as the first directory it
looked in — so the layout was agreed and only the staging was missing. Declaring
and its descriptor. Unpacked and run — a *member* archive, on its own, so the
executable sat at `<root>/bin/` — the converter refused a real capture and
named `<root>/share/usd-vrm-plugins/profiles/motion` as the first directory it
looked in, which read at the time as the layout being agreed and only the
staging being missing. The qualification is added in hindsight and the next
paragraph is why: that is one of two installed layouts, and the tool searched
the product's first only after 2026-08-30. Declaring
`directories: [bin, share]` did stage it, and was rejected: `directories:` names
subdirectories of the *member root*, so it would have put the layer's data inside
one tool's directory and the copy that shipped would have stopped being the file
Expand All @@ -727,13 +731,32 @@ rather than copied under any member root. The file that ships is the file
`scripts/check_motion_profiles.py` validates, which is what
`directories: [bin, share]` could not promise.

**What is proven is the staging, not yet the run.** The product archive carries
the profiles; nothing in this repository has yet extracted that product to a
prefix and driven `motion_bvh_convert` from it, so the artifact-only smoke this
paragraph exists to make possible is now *possible* rather than *passing*. That
distinction is the one to keep: v0.7.0 recorded the condition unmet because the
mechanism was missing, and the mechanism arriving is not the same thing as the
test existing
**The run happened on 2026-08-30 and it failed, which is why the distinction
above was worth keeping.** `scripts/artifact_only_bvh_smoke.py` packages the
product, verifies it, installs it to a prefix outside this repository, and
drives `motion_bvh_convert` there with no `--profile-dir` and no
`USDVRM_MOTION_PROFILE_PATH`. The profiles arrived exactly where this section
says — byte-identical to `profiles/motion/` — and the converter refused the
capture anyway, because `ost plugin product install` lands a tool member at
`<prefix>/tools/<member>/bin/` and the locator looked at
`<exe>/../share/usd-vrm-plugins/profiles/motion`, one directory too shallow
inside the product's own prefix.

The paragraph this replaces recorded that "the layout was agreed and only the
staging was missing", and the agreement was real but with a *different* layout:
it was measured on a member archive unpacked on its own, where the executable
does sit at `<root>/bin/`. Two installed layouts put the data in the same place
relative to the prefix and the tool at different depths inside it, so an
executable-relative rule serves one of them at a time. The locator now carries
both, the smoke passes — 853 frames at 50 Hz, 22 bound joints, from the artifact
alone — and it proves the profile it read was the installed one by moving that
file aside and requiring the refusal to come back.

**None of this changes the destination**, which is the part worth stating: the
contract in this section was right, `[[workspace.install_data]]` puts the files
there, and the defect was one reader of it. That is the argument for the smoke
rather than for more review — the search path was documented, the destination
was documented, the two were written from each other, and they still disagreed
([report 36](../reports/ost/36-2026-08-25-v0.22.3-canonical-runtimes-and-release-membership.md) §4).

That split is the one to check when a future reader arrives: a reader is in the
Expand Down
11 changes: 7 additions & 4 deletions docs/contributing/RELEASE_NOTES_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ VRMA motion clips as canonical `UsdSkelAnimation` data.
> §5 keeps optional network and SDK dependencies out of the core distribution —
> and they build from the source archive.
>
> **No member carries data.** `motion_bvh_convert` needs a producer profile from
> `share/usd-vrm-plugins/profiles/motion/`, which reaches no archive, so run it
> from a `cmake --install` prefix or pass `--profile` a path. The profiles ship
> in the source archive.
> **The product carries the motion profiles.** `motion_bvh_convert` needs a
> producer profile, and `ost plugin product install --prefix <dir>` puts the
> shipped ones at `<prefix>/share/usd-vrm-plugins/profiles/motion/` where the
> tool finds them with no flag — verified per release by
> `scripts/artifact_only_bvh_smoke.py`. No *member* archive carries them: a
> `motion_bvh` archive unpacked on its own is its two executables, so that route
> still needs a `cmake --install` prefix or a `--profile` path.

Each binary bundle carries a `buildInfo.json` stamp in its resources
(git commit / build OS / compiler / build type / OpenUSD version / schema
Expand Down
47 changes: 33 additions & 14 deletions docs/roadmap/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ checks are listed rather than remembered:
- [ ] the CHANGELOG names the **architecture** changes, not only the features:
two shared libraries extracted, a third adapter, and every adapter's
package config gaining a dependency it was missing;
- [ ] the artifact-only BVH smoke is green on **Linux and macOS**, not only on
the workstation that wrote it — it is a `release.yml` step, so a tag is
the first time those two cells run it, and the `workflow_dispatch --ref`
dry run below is what turns that from a surprise into a measurement;
- [ ] `scripts/check_docs.py`, `check_motion_profiles.py` and `verify_corpus.py`
are green, and `release.yml` is dry-run with `workflow_dispatch --ref`
before the tag — a green PR lane proves nothing about it.
Expand Down Expand Up @@ -228,13 +232,25 @@ checks are listed rather than remembered:
adapter artifact in CI, so whether a release carries them is open
([report 36](../reports/ost/36-2026-08-25-v0.22.3-canonical-runtimes-and-release-membership.md)
§2, §3).
- ⬜ **The profiles reach the product; the smoke that would prove it does not
exist.** Both halves of the staging are closed — the plain-CMake install
(2026-08-05) and the packaged one (2026-08-25, `ost` 0.22.3's
`[[workspace.install_data]]`, `data_files: 3`). **What remains is the test**:
nothing has extracted the product to a prefix and driven `motion_bvh_convert`
from it, so the entry stays open on a written smoke rather than on a missing
mechanism
- ✅ **The profiles reach the product, and the smoke that proves it found a
defect** *(2026-08-30: `scripts/artifact_only_bvh_smoke.py`)*. Both halves of
the staging were already closed — the plain-CMake install (2026-08-05) and the
packaged one (2026-08-25, `ost` 0.22.3's `[[workspace.install_data]]`,
`data_files: 3`) — and this entry stayed open on the test rather than on a
mechanism. **The first run of that test failed.** The profiles installed
byte-identically to `share/usd-vrm-plugins/profiles/motion/` and
`motion_bvh_convert` refused the capture anyway: `ost plugin product install`
lands a tool member at `<prefix>/tools/<member>/bin/`, and the locator's
installed-prefix rule was `<exe>/../share/…`, which is the layout of a *member
archive* unpacked on its own. The fix is one more search-path rule in
[ProfileLocator.cpp](../../tools/motionBvh/src/ProfileLocator.cpp); the run is
853 frames at 50 Hz through 22 bound joints, from the artifact alone, and it
ends by moving the installed profile aside and requiring the refusal to come
back — so "it found *a* profile" cannot pass for "it found the one this
product ships". Wired into `release.yml` beside the clean-install smoke, on
all three of that lane's cells — which means it inherits that lane's standing
caveat: **no PR event runs it**, so the only host it has been measured on is a
Windows workstation, and the first Linux and macOS runs happen at a tag
([report 36](../reports/ost/36-2026-08-25-v0.22.3-canonical-runtimes-and-release-membership.md) §4).

### Carried out of v0.7.0 — evidence an operator produces
Expand All @@ -258,13 +274,16 @@ None of these closes by writing code, and each is stated with what it costs.
with hashes, every measured statistic and no bytes — a session is a real
person's motion and a skeleton packet is a body measurement of that person.
Getting a publishable one needs the vendor's `BVH Sender`, not a device.
- ⬜ **Both paths running from release artifacts alone, profiles included.**
No longer blocked on the toolchain. Every member the two paths need is in the
product, and as of `ost` 0.22.3 so are the profiles. `mocopi_record` is not —
it left the aggregate by declaration when the exclusion stopped being a
version pin — so this run composes the product with the adapter's own artifact,
which `ost library package` can now produce. It stays open because the run has
not been performed.
- 🚧 **Both paths running from release artifacts alone, profiles included.**
**The recorded path is done** (2026-08-30): `motion_bvh_convert` converts a
real mocopi export from an installed product prefix with nothing from this
source tree on any search path, and the profiles it uses are the product's own
— see the packaging entry above. What is left is the **live** path, and it is
left for the reason it always was rather than a new one: `mocopi_record` is
not in the aggregate — it left by declaration when the exclusion stopped being
a version pin — so that run composes the product with the adapter's own
artifact, which `ost library package` can now produce, and nothing has
performed it.

### Still Motion Phase G

Expand Down
25 changes: 16 additions & 9 deletions docs/roadmap/recorded-motion-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,22 +587,29 @@ Still open:
characterisation, so a later rule has to change that test before it changes
the behaviour, and `motion_retarget` names the bone on stderr rather than
losing it in silence.
- **The profiles reach a packaged product; the artifact-only smoke does not
exist.** Both halves of the staging are closed — the root project installs
`profiles/motion/*.yaml` to `share/usd-vrm-plugins/profiles/motion/`
(2026-08-05, verified against a scratch prefix), and `ost` 0.22.3's
`[[workspace.install_data]]` gives the mapping a product-level owner, with the
aggregate reporting `data_files: 3`
- **The profiles reach a packaged product and the artifact-only smoke runs it**
*(2026-08-30, `scripts/artifact_only_bvh_smoke.py`)*. Both halves of the
staging closed first — the root project installs `profiles/motion/*.yaml` to
`share/usd-vrm-plugins/profiles/motion/` (2026-08-05, verified against a
scratch prefix), and `ost` 0.22.3's `[[workspace.install_data]]` gives the
mapping a product-level owner, with the aggregate reporting `data_files: 3`
([report 36](../reports/ost/36-2026-08-25-v0.22.3-canonical-runtimes-and-release-membership.md) §4).
Through v0.7.0 it did not: a `motion_bvh` archive was its two executables and
Through v0.7.0 neither did: a `motion_bvh` archive was its two executables and
its descriptor, so a converter unpacked from a product found nothing on its
executable-relative search path and refused every file it was given — the
specific consequence [WORKSPACE.md §5](../architecture/WORKSPACE.md) put the
profiles beside the tools to prevent, and an `ost` ask rather than something a
`--profile-dir` flag closes, because "works if you pass a flag naming a
directory the artifact does not contain" is not an artifact-only smoke.
**What stops BVH-3 closing is now only that nobody has extracted the product
and run the converter from it.**

**The smoke then failed for a second reason, which was ours.** The data
arrived byte-identically and the converter still refused: an installed product
puts a tool member at `<prefix>/tools/<member>/bin/`, and the locator's
installed-prefix rule assumed `<prefix>/bin/`. So this entry closes on two
fixes in different layers, and the sequence is the point — the packaging ask
was real, closing it was not sufficient, and only running the thing
distinguished the two. The passing run converts the committed 17-second mocopi
export from the prefix alone: 853 frames at 50 Hz, 22 of 27 joints bound.

Two measured facts worth keeping, because both are easy to assume the other way:

Expand Down
Loading
Loading