Hierarchical ShaderProfile storage with walk-up inheritance.
Same inheritance semantics as PhosphorAnimation::ProfileTree but carrying ShaderProfile payloads instead of Profile. The two trees share the same dot-path namespace (ProfilePaths) so a consumer can resolve both motion and shader config for the same event path.
Walk-up inheritance
For "window.appearance.open", resolve() walks:
"window.appearance.open" (leaf override)
"window.appearance" (appearance contract group — the "All Appearance" node)
"window" (category — the all-windows node)
"global" (the assignable root node — a real chain member a user can store an override on)
- the tree baseline (setBaseline(), NOT the same thing as 4)
- library default (empty ShaderProfile — no effect)
Steps 4 and 5 are two independent levels and cutting one does not cut the other. That distinction is load-bearing for both exceptions below: an isolation that drops only the baseline still lets whatever the user assigned on the global node cascade in.
At each step, engaged optionals replace the accumulator. Unset fields pass through.
EXCEPTION 1, a leaf that inherits NOTHING: the interactive-drag leaf (window.movement.move) and the tab-switch leaf (scrolling.tabSwitch) take no inherited shader — resolve() reads only their direct override. The two are in the predicate for different reasons. The drag leaf's ancestor picks are single-surface crossfade packs by construction and cannot drive the held drag transition (see EventClassMove in ProfilePaths.h). The tab leaf's class is OPT-IN rather than universal-permissive — a pack must declare appliesTo: ["tab"] to be offered (see EventClassTab in ProfilePaths.h) — so every pack a picker offers on an ancestor FOR THAT ANCESTOR'S OWN SAKE is refused here. Note the leaf has three levels above it, not one: parentPath("scrolling") is global, not empty, and the baseline sits above that. The one pack that would survive the gate is a HYBRID declaring tab beside the ancestor's class (["tab","appearance"] is assignable on global, ["tab","strip"] on scrolling), so isolating the leaf is a policy choice for that case rather than a proof: a hybrid the user engaged for window appearance must not silently start driving tab swaps. For everything else the refusal is structural, and it happens when the transition BEGINS (shaderEffectAppliesToEventPath), not at install. Predicate: shaderPathResolvesInIsolation().
EXCEPTION 2, a SUBTREE that inherits normally within itself while nothing above it reaches in: today the shell root. Inheritance between the root and its legs works as usual, so a pack on the root cascades to them, but resolve() substitutes an empty baseline AND trims the chain back to the root, closing steps 4 and 5 together. Predicate: shaderPathIsolationRoot().
Thread safety
Value type, not internally synchronized. Same as ProfileTree.