Add ArmIKLayer, AudioVisemeDriver, and VMC Protocol support for device-driven avatars - #434
Add ArmIKLayer, AudioVisemeDriver, and VMC Protocol support for device-driven avatars#434arkavo-com wants to merge 10 commits into
Conversation
Drives each arm toward a world-space hand target with elbow hints and per-target blend weight, and curls fingers about model-space axes converted into each phalanx's rest frame. Rebuilds the chain from cached rest transforms under the live parent every frame so solutions are base-pose relative and stable across frames; releases write an explicit identity delta so a cleared target returns the arm to rest. Part of #433. Refs arkavo-org/Creator#31. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
Classifies PCM windows into the five VRM visemes from a two-band formant centroid estimate scored against canonical F1/F2 pairs, with a noise gate and loudness curve. The driver keeps a rolling window fed from the audio thread, smooths through FilterManager, and writes the controller like ARKitFaceDriver. No AVFoundation in the library. Part of #433. Refs arkavo-org/Creator#33. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
OSC 1.0 message and bundle decode/encode. VMCDriver applies Bone/Pos, Root/Pos, Blend/Val+Apply, OK and T to a model and expression controller with the Unity-to-model X flip, rest-relative rotation composition, VRM 0.x blend aliases, and the Unity thumb-chain shift. VMCEncoder emits the inverse for sending; VMCReceiver is a thin UDP NWListener. Part of #433. Refs arkavo-org/Creator#36, arkavo-org/Spatial#89. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
… VRM 0.x asset Drops the wrist bones from ArmIKLayer.affectedBones since the layer never writes them, makes the four-finger curl axes configurable, and adds a guarded fixture test that loads a VRM 0.x model and confirms a Unity yaw swings the left hand forward under the X-flip convention. Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
|
✅ Claude Code Review ran on The completed review reported no tool permission denials. |
- OSCArgument.intValue uses Int(exactly:) so NaN, infinity, or out-of-range floats off the wire return nil instead of trapping. - Bundle decoding caps nesting at OSCPacket.maxBundleDepth (8) and sub-readers share the datagram bytes instead of copying per level; OSCPacket.messages flattens iteratively. - VMCReceiver recreates its readiness semaphore per start() and ignores state callbacks from a superseded listener, so waitUntilReady cannot consume a stale signal across stop()/start(); sender flows are capped at maxConnections (default 8) with oldest-first eviction. Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
… programs The reviewer was denied Bash 28 times on PR #434 because pipeline segments like grep, tail, cut, sort, and xargs were not on the allowlist. Adds those read-only tools and makes the diagnostics step list the program names (never arguments) of denied Bash calls so future gaps are visible from the heartbeat comment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
Sending from two flows at once let the second flow evict the first before its datagram was read under parallel test load. Wait for each flow's packet in turn before asserting the cap. Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
d09b3de to
d6b79db
Compare
Moves the onError call past the listener identity guard so a superseded listener failing during a restart cannot emit a spurious error. Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
…etters - VMCReceiver captures its listener weakly in the state handler and cancels in deinit, so dropping a started receiver releases the port. - VMCDriver caps distinct pending blend-shape names at 256 per Apply cycle; updates to existing names are still accepted. - VMCDriver.frame and counters, and VMCReceiver.boundPort and counters, are lock-taking computed getters over private storage. Suites pass under ThreadSanitizer. Part of #433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj
Code Review ✅ Approved 3 resolved / 3 findingsComprehensive device-driven avatar support with ArmIKLayer for IK-solved arm poses, AudioVisemeDriver for mic-driven lip-sync, and VMC Protocol implementation (OSC codec, driver, encoder, receiver). VMCReceiver idle connection leak and stale ready signal consumption were fixed; onError timing issue was dismissed. No open issues remain. ✅ 3 resolved✅ Quality: VMCReceiver never releases idle UDP connections
✅ Edge Case: VMCReceiver.start()/waitUntilReady can consume a stale ready signal
✅ Edge Case: onError fires before listener identity guard on .failed
Was this helpful? React with 👍 / 👎 | Gitar |
Summary
Library work for the Mac "no-device avatar" path tracked in arkavo-org/Creator#40 (VMagicMirror parity). Three additive subsystems, no renderer or shader changes.
Closes #433. Refs arkavo-org/Creator#31, arkavo-org/Creator#33, arkavo-org/Creator#36, arkavo-org/Spatial#89.
ArmIKLayer (
Animation/Layers/ArmIKLayer.swift)AnimationLayerat priority 7 (aboveArmCounterbalanceLayer) that solves shoulder and elbow rotations toward world-spaceHandTargets withTwoBoneIKSolver, plus per-fingerFingerCurl.initialize(with:)under the parent's current world matrix, so deltas are base-pose relative and a constant target gives a constant pose across frames. This matters because the compositor composesbase * deltawithout resetting bones between frames.inverse(W) * delta * W), so it holds on rigs whose finger bones are not hand-aligned.AudioVisemeAnalyzer / AudioVisemeDriver (
Audio/)[Float]in, VRM viseme weights (aa ih ou ee oh) out. Formant heuristic: energy-weighted spectral centroids in two bands scored against canonical F1/F2 pairs in log-frequency space, with noise gate and loudness curve. Direct DFT on the bins of interest, no Accelerate or AVFoundation.push), smooths per viseme throughFilterManager, and writes the controller likeARKitFaceDriver(apply(to:)/update(samples:controller:)).VMC Protocol (
VMC/)OSCPacket/OSCMessage/OSCBundle: OSC 1.0 decode and encode, nested bundles, all standard type tags.VMCDriver:/VMC/Ext/Bone/Pos,/Root/Pos,/Blend/Val+/Blend/Apply(commit semantics),/OK,/T. Rotations are composed rest-relative (rest * inverse(restWorld) * delta * restWorld). Unity to model space is a reflection of X for every loaded model because the loader already rotates VRM 0.x to +Z-facing;flipZis kept for raw 0.x data. Maps UnityHumanBodyBonesnames including the thumb chain shift (Unity Proximal/Intermediate/Distal → VRM Metacarpal/Proximal/Distal) and VRM 0.x blend aliases (Joy,A,Blink_L, ...).VMCEncoder: inverse mapping for sending; a frame built from a model round-trips throughVMCDriverto the same pose.VMCReceiver: thin UDPNWListener, port 0 supported for tests.Docs
DeviceDrivenAvatarslinked from the catalog; README bullets.Test plan
ArmIKLayerTests(15): hand-on-target for both arms within 5 mm, stability over consecutive frames and after target changes, unreachable clamping, weight blending, elbow hint plane, finger curl by position for left/right/thumb/fist, release restores rest, priority above counterbalance.AudioVisemeTests(12): each prototype vowel classifies to its viseme from synthetic two-tone input, silence and gate, formant tracking, volume saturation, driver write/decay/gain/small-buffer accumulation.OSCPacketTests(10): hand-built byte layouts for a,sfmessage and a#bundle, round trips for all types, error paths.VMCDriverTests(16): name mappings both directions, coordinate flips, Unity yaw 90° on the left upper arm lands the hand 0.5 m forward, rest-relative correctness on a twisted rest bone, blend commit-on-Apply, staleness, encoder round trip, UDP loopback on an ephemeral port.swift test --parallel --num-workers 14 -j 16 --disable-sandbox.metalchanges, so no metallib rebuild orAvatarSample_U.pngregeneration.Design notes for reviewers
.blend(weight)intentionally replaces the counterbalance brace's additive arm deltas while a target is set.🤖 Generated with Claude Code
https://claude.ai/code/session_01FgZDdzrNEtPmbMupyyUTXj