09 / 14 · certificates

Palette Ceiling

A certified upper bound on how separable any N-color palette can be, worst case across CVD, bracketed by a witness palette.

figure generated by the instrument itself. hover to let the light in.

The problem

Published figures for how many distinguishable colors a display can show are napkin math: sphere-packing constants applied to the volume of a color solid, for one observer under one light. They answer nothing about your case. Given twelve classes and a requirement to survive protan and deutan vision, what is the best minimum pairwise separation any palette could possibly achieve? Without that ceiling you cannot tell whether a disappointing palette is a bad solve or an impossible ask.

The method

The tool samples the sRGB cube on a deterministic lattice, gamut_steps per axis, and views every point through normal vision and each declared CVD profile (Machado 2009), measuring separation in the lab's worst-case metric: the minimum ΔE_OK across all views. The certificate is a covering argument. If K balls of radius r cover the sampled gamut and K is less than N, any N colors must place two in one ball, so no N-palette can exceed a minimum pairwise separation of 2r; binary search on r finds the tightest ceiling the sample supports. A maximin solver then constructs a witness palette as the floor, so the true optimum is bracketed inside [floor, ceiling] with the gap reported, and an optional threshold walks N upward to report the largest class count the solver could still satisfy.

What it promises

  • The ceiling is computed, not estimated: a covering argument over the sampled gamut in the worst-case metric across normal vision and every declared CVD profile, with the sample density reported alongside it.
  • The floor is constructive: a real witness palette achieving achieved_floor_delta_e_ok exists, so the truth lies inside [floor, ceiling] and the gap is stated rather than split.
  • Given a threshold, feasible_n_at_threshold reports the largest class count at which the solver still met it, a demonstrated palette, not an extrapolation.

Calling it

{
  "tool": "certify_palette_ceiling",
  "arguments": {
    "n_classes": 12,
    "cvd_profiles": [
      {
        "type": "protan",
        "severity": 1
      },
      {
        "type": "deutan",
        "severity": 1
      }
    ],
    "threshold_delta_e_ok": 0.06
  }
}

Clone the maryslab repo, run cd mcp && npm install && npm run build, then claude mcp add maryslab -- node mcp/dist/server.js

A worked example

What is the best separation any 6-colour palette could achieve, and how close does a real one get?

what was asked

{
  "n_classes": 6,
  "cvd_profiles": [],
  "gamut_steps": 7
}

what the engine answered

{
  "n_classes": 6,
  "certified_ceiling_delta_e_ok": 0.6714805746078493,
  "achieved_floor_delta_e_ok": 0.2963856329826682,
  "gap": 0.3750949416251811,
  "feasible_n_at_threshold": null,
  "gamut_sample": {
    "steps": 7,
    "points": 343
  },
  "prior_art_note": "Published \"how many distinguishable colors\" figures are sphere-packing estimates for one observer under one light. This computes the bound for YOUR class count under the worst case across the declared CVD profiles, and pairs it with a constructed witness palette, so the true optimum is bracketed.",
  "note": "Ceiling from a covering argument over a finite gamut sample: if K(r) balls cover the gamut and K < N, two of any N colors share a ball, so min separation <= 2r. A denser gamutSteps tightens the ceiling; the floor is a constructed witness palette, so the truth lies inside [floor, ceiling].",
  "meta": {
    "engine_version": "0.4.1",
    "engine_backend": "reference-ts",
    "dataset_snapshot": "usgs-splib07a-1",
    "compute_device": "cpu"
  }
}

how to read it

The truth sits between achieved_floor (a palette that exists) and certified_ceiling (a bound from a covering argument). A wide gap is honest uncertainty, not a result.

Produced by running certify_palette_ceiling at build time (27 ms). Long arrays are truncated for reading; nothing else is edited.

Known limits

  • The ceiling depends on gamut sample density: the cover is built on a finite lattice, 2r is exact for the sampled points, and off-lattice colors are covered only up to the lattice's slack, so gamut_steps is part of the claim and a denser grid tightens it.
  • The floor is whatever the seeded maximin solver found under your budget, a constructed palette rather than a proven optimum; a wide gap is honest uncertainty about where the true optimum sits.
  • feasible_n_at_threshold stops at the first class count the solver fails, so it certifies feasibility up to that N, not impossibility beyond it.