arkavo-org / arkavo-org/VRMMetalKit

VRMExpressionController: add public getter for preset/custom weights

Open Beginner friendly
#273 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
6
Forks
2
Avg merge
18h 51m
Merged PRs (30d)
26

Description

## Summary

`VRMExpressionController` exposes `setExpressionWeight(_:weight:)` and `setCustomExpressionWeight(_:weight:)` publicly, but the corresponding read side is missing. The current weights live in `private var currentWeights: [VRMExpressionPreset: Float]` and `private var customCurrentWeights: [String: Float]` (`Sources/VRMMetalKit/Animation/VRMMorphTargets.swift:455-456`) with no public accessor.

## Why this is needed

Downstream tooling that drives expressions through `setExpressionWeight` and then needs to observe what VMK actually stored — including the post-clamp value, and including weights that `AnimationPlayer` writes during VRMA playback — currently has no way to read it back without subclassing or maintaining a duplicate mirror.

Concrete use case: [arkavo-org/vrm-conformance](https://github.com/arkavo-org/vrm-conformance) is wiring a `dump_expression_weights` adapter op so the conformance suite can verify the VRM 1.0 viseme `morphTargetBinds` weight coercion fix shipped in [0.15.2 / PR #272](https://github.com/arkavo-org/VRMMetalKit/pull/272) end-to-end. The verification model is "drive a viseme weight, dump back, compare across renderers" — three-vrm exposes `expressionManager.getValue(name)` for exactly this; VMK's symmetry gap forces an adapter-side mirror that loses fidelity when `AnimationPlayer` writes weights internally during VRMA playback.

## Proposed API

```swift
extension VRMExpressionController {
/// Current weight of a preset expression. Returns 0 when the preset
/// has not been set (matches the `init()` default).
public func weight(for preset: VRMExpressionPreset) -> Float {
currentWeights[preset] ?? 0
}

/// Current weight of a registered custom expression. Returns `nil`
/// when the name has not been registered via
/// `registerCustomExpression(_:name:)`.
public func weight(forCustom name: String) -> Float? {
customCurrentWeights[name]
}
}
```

Two methods, both pure reads, no behavioural change. Matches the existing setter symmetry (`setExpressionWeight` / `setCustomExpressionWeight`).

## Alternative considered

Exposing the dictionaries directly (`public private(set) var currentWeights`) is structurally simpler but commits to the storage shape as API. The accessor form lets `VRMExpressionController` re-implement storage (e.g. switch to an array indexed by `allCases`) without breaking callers.

## Severity

Low. Workaround exists (adapter-side mirror of `setExpressionWeight` calls), but the workaround is unsound when `AnimationPlayer.update(deltaTime:model:)` writes weights as part of VRMA playback — adapter sees neither the call nor the result.

## Test coverage

Single round-trip test in `VRMExpressionControllerTests` (or wherever the controller's existing tests live):

```swift
let c = VRMExpressionController()
c.setExpressionWeight(.aa, weight: 0.7)
XCTAssertEqual(c.weight(for: .aa), 0.7, accuracy: 1e-6)
c.setExpressionWeight(.aa, weight: 1.5) // clamps to 1.0
XCTAssertEqual(c.weight(for: .aa), 1.0, accuracy: 1e-6)
XCTAssertEqual(c.weight(for: .ih), 0.0) // unset → default
```

## Related

- [VRMMetalKit 0.15.2 release](https://github.com/arkavo-org/VRMMetalKit/releases/tag/0.15.2) — viseme weight coercion fix
- [arkavo-org/vrm-conformance#13](https://github.com/arkavo-org/vrm-conformance/issues/13) — adapter-side wiring task that surfaced this gap

Contributor guide

Open the contributing guide

Research direction

Start in Sources/VRMMetalKit/Animation/VRMMorphTargets.swift around VRMExpressionController and its currentWeights and customCurrentWeights storage. Read the existing setter and registration behavior, then add the two read accessors described in the issue. Run the existing VRMExpressionControllerTests, adding the preset round-trip, clamping, and unset cases to verify the stored values.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
computer-graphics
Issue type
Feature
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.