Palette and colour promises

Optional

A palette keeps the colours that define a game's look in one named place. Prose points at the whole set or at one named colour, and never copies a hex value. The example is a lantern-lit dungeon crawler.

direction.json, under palette

{
  "palette": {
    "lantern": [
      "#1A1B2E",
      { "shadow": "#2E3450" },
      { "flame": "#E8A13C" }
    ]
  }
}

Each entry is either a bare hex value or a named colour. Order carries no meaning. A bare hex needs no name until something points at it. In presentation prose, palette.lantern names the whole set and palette.lantern.flame names the flame colour. Palette names may contain dots. A citation is matched as a whole palette name first. If none matches, its last part is read as a colour name.

Colour promises#

A colour promise adds a measurable limit for one named colour.

direction.json, under colors

{
  "colors": {
    "lantern-flame": {
      "is": "palette.lantern.flame",
      "within": 6,
      "where": "the lantern the player carries"
    }
  }
}

is points at the colour. within says how far the colour on screen may differ from it. The difference is measured as a person sees it (the CIEDE2000 scale). Use about 2 when a UI mark must look unchanged, about 6 when a lit object may appear as a nearby shade, and 10 or more when a clearly different colour is acceptable. within: 0 means exactly this colour. where says which visible thing, or which view, the promise covers.

Contrast promises#

A contrast promise says two declared colours must stay apart.

direction.json, under contrast

{
  "contrast": {
    "flame-vs-shadow": {
      "colors": ["palette.lantern.flame"],
      "against": "palette.lantern.shadow",
      "at_least": 4.5,
      "where": "the lantern flame against the cellar"
    }
  }
}

Each colour in colors is measured against the one colour in against. at_least is a contrast ratio as defined by the web accessibility guidelines (WCAG): 4.5 is the usual floor for readable text, 3 for large shapes, and 7 is strict. The validator (the checking tool) checks your declared colours against your floor before any build exists.

Timing promises#

A timing promise says a visible event lasts as long as a number in tuning.json.

direction.json, under timing

{
  "timing": {
    "lantern-pulse": {
      "key": "feel.lantern_pulse_seconds",
      "where": "each bright pulse of the lantern"
    }
  }
}

The covering test#

Every colour, contrast and timing promise must be covered by an acceptance test in 05-build-plan.md. The test names the promises it covers in direction_claims. Only that field decides coverage; the sentences in then are instructions for whoever runs the test.

05-build-plan.md, the test covering all three promises

{
  "type": "scenario",
  "given": "the player carries the lantern through the cellar",
  "when": "the lantern is shown against the darkest cellar wall",
  "then": ["the flame colour matches `colors.lantern-flame`", "the flame stays distinct from the cellar as `contrast.flame-vs-shadow` requires", "each pulse lasts as long as `timing.lantern-pulse` requires"],
  "direction_claims": ["colors.lantern-flame", "contrast.flame-vs-shadow", "timing.lantern-pulse"]
}

The test points at each promise. Do not copy the promise's numbers, place or conditions into the test. A promise that no test covers is an error, and the validator refuses it.

Where a colour appears, how close it must stay, and how long an event lasts belong to the promises. The validator checks what it can from the file, such as a contrast pair below its floor. The covering test checks each promise when the build is run.

Full rules: OpenGDD specification, §9 and §6.

← Art direction · All chapters · Personalization questions →