Utility as a max of experts
A candidate is as good as its best lens thinks it is. Two more obvious designs cannot represent a cross-island comparison at all.
The form
style lenses, each a linear functional on the standardized feature vector. Utility is the maximum, not a weighted mixture.
At this reduces exactly to Bayesian linear regression on , which is a useful property: the mixture is a strict generalization with no special-casing at the boundary.
pub fn utility_mix(&self, phi: &[f64]) -> f64 {
self.theta.iter()
.map(|t| dot(t, phi))
.fold(f64::NEG_INFINITY, f64::max)
}
Why a maximum
Taste is multi-modal. One person can love dark drones and bright plucks ("ambient-me" and "acid-me"), and those are not points on one axis. A single linear utility would average them into a preference for neither, and would then be confidently wrong about both.
The max form gives each island its own lens, and every judgement, including a duel across two islands, compares candidates on the shared scale . A dark drone and a bright pluck are both scored, each by whichever lens likes it most, and the comparison is well-formed.
Two rejected designs, and why
A per-session style latent
"One mood per session — sample which lens is active, then use it."
Fails because it cannot represent several islands inside a session. A user who auditions a pad, then a bass, then a pad in one sitting is not switching moods; they have two preferences at once. Whenever the session's latent is wrong for the current candidate, every observation in that session is scored by the wrong lens.
A per-observation marginalized lens
"Marginalize over which lens judges each observation."
Fails on a sharper point: it forces both duel items through the same lens, so a cross-island comparison is unrepresentable. There is no lens under which "the drone beats the pluck" is a sensible statement if the drone lives in lens 1 and the pluck in lens 2, and a duel between them is exactly the question the acquisition rule will ask.
This is not a theoretical objection. A synthetic bimodal user exposed it: the marginalized mixture failed to beat . Adding capacity made the model no better, which is the signature of capacity the likelihood cannot use.
What max-utility buys structurally
There are no discrete latent sites at all. No lens assignment to sample, no
categorical variables, no label-switching during inference to fight. Every
site in the model is an f64, which means fugue's generic adaptive single-site
MH applies unchanged — no custom kernel, no Rao-Blackwellization.
Label permutation is resolved post hoc instead, by
TastePosterior::aligned.
is an upper bound, not a claim
by default (SessionConfig::k_styles), and the fitted number of live
lenses grows with evidence.
Nothing enforces that; it falls out. A lens with no evidence to explain stays
near its prior, and style_share reports what fraction of the pool each lens
actually claims as its best. A lens claiming ≈0% is idle: the user's taste
has fewer islands than , and the app dims it rather than inventing a name
for it.
So is capacity, and the data decides how much gets used.
The prior, and the correction forces
The factor is standard: with for a standardized vector, it makes the prior utility of a candidate roughly unit-variance, so likelihood scales stay sane at any feature count.
The factor is the correction the max form forces, and it is easy to miss.
Under the prior each is marginally , so is the maximum of iid standard normals — whose standard deviation falls with :
| 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|
| 1.000 | 0.826 | 0.748 | 0.701 | 0.669 |
The mean shift cancels in duels (both sides shift equally) and is absorbed by and the cutpoints elsewhere. The variance shrinkage does not cancel. Left uncorrected, drops from 2.0 at to 0.90 at — so growing mid-session would quietly make the model less able to express a strong preference.
That is the opposite of what adding capacity should do, and it would present as "the model gets vaguer the longer I use it".
Dividing by restores invariance: is the same at every .
What the interface reads off this
| Quantity | Is |
|---|---|
utility_mix(z) | of over posterior draws — the glow and size on the taste map |
utility(z, k) | Lens 's opinion specifically |
best_style(z) | Which lens claims this candidate — the hue on the map |
responsibilities(z) | Posterior probability that each lens is the best one for this candidate |
style_share(pool) | Per-lens share of the pool, averaged over candidates |
prob_prefers(a, b) | — the bank row's percentage |
responsibilities is a posterior distribution over which lens applies, which
is strictly more informative than an argmax and is what lets a candidate sit
visibly between two islands.