The colours were easy. Teaching the metric how Wada combines them took five tries.
I set out to generate palettes in the spirit of A Dictionary of Color Combinations. The colours were easy. Learning how Wada combines them took five tries — and each failure taught me I was measuring the wrong thing.
The real story is the metric
The palettes only improved because my yardstick did. Here's how it evolved.
Harmony score
Density of each pair's relationships among his combos.
Diversity volume
Spread = the volume the colours span.
Contrast number
Hit Wada's measured ΔE, ≈0.28.
Value vs chroma
Split contrast into value and saturation.
Learned quality vector
A tuned vector of Wada-ness; sample, don't maximise.
The first sampler ranked colours by a learned harmony and chose with MMR. Before leaning on it, I ran a simple test: show the score one real Wada combination and one random pairing of his colours — can it tell which is real? A perfect judge is right 100% of the time; a useless one, 50% — a coin flip.
Harmony scored a coin flip. Another way to see it: plot the scores it gives to real combinations versus random pairings. A good metric pulls them apart; harmony piles them on top of each other. The gamut (which colours, not how they're combined) is the signal that actually separates.
real Wada random
the two piles overlap → it can't tell them apart
real Wada random
the piles separate → this one can
MMR is sometimes fine. But it's only as reliable as the score steering it — and that score is a coin flip, so good rolls are luck, not skill.
If I can't score harmony, lean on structure: a Determinantal Point Process, the canonical diverse-subset picker. But my kernel weighted "quality" so hard that the DPP huddled in the dense, muted core of the gamut. Every palette became three mid-tones at the same lightness — flatter than random.
I measured Wada's real contrast (mean ΔE ≈ 0.28) and tuned the kernel to hit it. The number matched; the palettes turned into loud primaries — because a single contrast number doesn't care how you reach it, and the cheapest contrast to buy is raw saturation.
Act IV · the diagnosis
I stopped tuning and looked. Take every pair of colours inside a combination and ask how one turns into the other: does it change in value (lighter/darker) or in saturation? Plotted, the answer is a tall, thin cloud — Wada moves up and down, almost never sideways. His contrast is ~13× more value than chroma.
My isotropic kernel treated both directions the same, so it happily traded value for the saturation it should have left alone. The fix had to make the algorithm see colour the way the cloud is shaped.
The gamut, and why it matters
Plot all 159 colours by lightness and saturation. They hug the left — low chroma — at every lightness. That muted band is the gamut. The whole job is: stay in the band, but travel up and down it.
Left, the garish attempt breaks right out of the band into saturation. Right, the final sampler stays in the band and spreads top-to-bottom — contrast from value, exactly like Wada.
The anisotropy is real — but baking it into a bespoke distance metric had a side-effect the two-sample test caught: it skewed palettes warm and clustered their hues. The cleaner fix puts the intelligence in the quality: a multidimensional, Optuna-tuned vector of Wada-ness — manifold proximity, value-conditional typicality (so dark colours aren't punished for being dark), mutedness, hue-fit. With that, diversity can be measured plainly (isotropic), and drawing a k-DPP sample instead of the single greedy-best yields the dark tail, the muted chroma, and a naturally graded saturated accent — no hand-tuned metric required.
The whole journey, on one map
Each attempt placed by what it actually made: saturation across, value contrast up. Timid sinks to the floor; the contrast-by-number fix overshoots into saturation; only the final recipe — isotropic diversity + the learned quality vector — lands on Wada: high value contrast at low chroma.
One more fix underneath
My candidate colours had been blobs jittered off the 159 named points — which sprayed a third of them out past the muted band, into saturation. I replaced it with sampling the gamut region directly.
only 65% land in the muted band — the rest leak right, into saturation
100% in-band, by construction — and more varied
Alternatives considered & discarded
More harmony features
Three different feature sets all scored ≈ a coin flip. Convergence, not a gap — more would just overfit 348 combos.
A Gaussian-process surrogate
No scalar function to regress — the dependence is a covariance, which is a copula's job, not a GP's. I did try the copula; see the honest-ceiling section.
A hard chroma cap
Clipping saturation works but throws away Wada's value–chroma correlation. The learned metric keeps it.
Pure diversity
Drop quality entirely and the picker grabs garish gamut-edge outliers. Quality still has to gate what's allowed in.
Just widen the jitter
Bigger blobs clump harder around the 159 points and leak further out. The gamut region is the principled fix.
Did I actually become Wada?
Matching a few averages is not the same as matching the distribution. The real check: train a classifier to tell generated palettes from real Wada combinations on perceptual (OKLab) features. If it can't — AUC near 0.5 — they're indistinguishable; near 1.0 it has found an obvious tell.
At first it was trivial (AUC ≈ 0.95): my palettes ran too warm, their hues clustered, their colours washed. The fixes — make colour quality a learned vector of Wada-ness (Optuna-weighted, with a chroma term), measure diversity isotropically, and sample the dark tail — pulled it to 0.71, with every marginal landing on Wada:
AUC is just this: line every palette up by the classifier's score and ask how often a real Wada outranks one of mine. At 0.71 the two populations overlap heavily — and the paired examples below, which the classifier scored the same, are genuinely hard to call (top of each pair = the book, bottom = mine).
| feature | Wada | before | after |
|---|---|---|---|
| warm balance (frac warm) | 0.52 | 0.65 | 0.57 |
| hue spread | 0.40 | 0.15 | 0.40 |
| chroma (saturation) | 0.10 | 0.08 | 0.10 |
| saturated accent (chroma range) | 0.10 | 0.07 | 0.10 |
| dark reach (min L) | 0.45 | 0.51 | 0.44 |
Not a perfect 0.5 — a from-scratch generator still leaves a faint fingerprint (a slightly narrower chroma range) — but mine now sits inside Wada's distribution rather than beside it.
How close can I get? The honest ceiling
Every marginal matches, but AUC ~0.70 means a joint-structure residual remains: which lightness pairs with which chroma and hue. To close it I modelled the joint directly — a Gaussian copula (Sklar's theorem: keep the exact marginals, add only the dependence — the covariance idea a Gaussian process is built on, aimed at generation) and a GMM — and validated honestly with a train/test holdout.
| approach | AUC | novelty | verdict |
|---|---|---|---|
| perturb real combos (jitter) | 0.45 | 0.03 | matches — but only by copying (no novelty) |
| k-DPP sampler (shipped) | 0.70 | 0.09 | novel + controllable — the honest best |
| Gaussian copula | 0.61* | 0.09 | *in-sample only; held-out it overfits to ~0.71 |
| Gaussian mixture (GMM) | 0.74 | 0.12 | worse — re-models the marginals I'd matched |
The trap is the last two columns. Perturbing real combos hits AUC 0.45 — but novelty 0.03 means it's just copying. The copula dazzles in-sample (0.61), but held-out it overfits: a 9–12-dimensional joint estimated from ~160 training combinations memorises their quirks. A held-out global Optuna found no real gain over the defaults either.
So the honest verdict: with 348 curated combinations, AUC→0.5 is reachable only by memorising — ~0.70 is the ceiling for genuinely novel generation. The one lever that moves it is more data (Wada's Volume 2 and the original six), at which point the copula and a global tune stop overfitting and become real wins. Until then, the controllable sampler is the right answer.
Where we landed
Choose a hue from the wheel. The sampler builds a palette centred on it — your colour forced in, surrounded by its Wada-fit companions. Beside it, the closest real combination from the book: the one whose nearest colour matches your pick.
a hue