Log inGet started
World

Cold Start

Branchmain
Commitcfc095f0fc

README.md

asc_palette — the §5 palette → display seam

ASCENSION's §5 authors the part in sRGB hex and says what every phrase's sky should read as on screen. This module is the arithmetic that makes that true, in one place, so no aspect re-derives it and no aspect guesses.

It draws nothing and decides nothing about the picture. It owns three facts.

1. The space the sky's ramp is evaluated in

§5 gives a phrase two ends — a cold zenith and a warm horizon — and rule 1 forbids a third hue between them. Both cannot hold, and the arithmetic says which one bends. The straight display-space line from P5's #2E4560 to its #FFC98A is

r(u) = 46 + 209u    g(u) = 69 + 132u    b(u) = 96 + 42u

and at u = 0.30 those are 108.7 / 108.6 / 108.6 — a neutral at value 109, which is the #808080 §5 names as proof the grade has failed. So the ramp must leave the chord. Leaving it on one side of the hue circle gives magenta, which rule 1 excludes by name; leaving it on the other gives a teal-green bridge. Those are the only two options: they are the two sides of a line.

The crossover is therefore authored, deliberately, on the cold-to-teal side, with its hue rotated along the descending arc so the magenta family is geometrically unreachable rather than swept out afterwards, and its chroma set to the most sRGB can hold at its lightness (gamutFit) — being as far from the neutral axis as the gamut allows is exactly the anti-neutral position.

Two things keep the bridge from becoming a hue of its own:

  • It is confined. HORIZON_TIGHT holds the warm end near the horizon and ZEN_RATE brings the phrase's own zenith back within a few degrees above it, so the crossing occupies a narrow band rather than a third of the sky.
  • It is dark. MID_L keeps its lightness near the zenith's, not the horizon's, so it reads as the shadowed part of a sky.

And it is azimuthal, per §6's contre-jour: opposite the sun there is no burn, so both the warm end and the bridge fade to a darkened cold sky and that whole side of the frame stays one hue family. The azimuthal fade travels through the bridge for the same reason the vertical one does — a straight line from a cold sky to a warm one passes next to the neutral axis.

2. The display ceiling, derived

§6 permits exactly one element per bar above 1.0 linear and clamps everything else below §2's bloom threshold of 1.4. The sky is never that element. But §5's bright phrases ask the sky for #FF9A5A and #FFC98A, whose brightest channel is display 1.0, and the scene-linear value that lands on display 1.0 is 7.24 — five times the threshold.

ceilFor(sceneExposure) is the display value that lands on SKY_SCENE_CAP (1.35, under the threshold by construction) at this frame's exposure. It is not a constant to sweep: raise expEV and the ceiling rises with it. At expEV = +1.2 the ceiling is 0.985 and §5's horizon hexes land within 4/255. At expEV = 0 the same ceiling is 0.916 and they cannot be reached at all — which is where a washed horizon comes from.

3. The inverse of the chain

The engine runs, in this order:

motion_blur 30 · bloom 40 (threshold on max(r,g,b), pre-exposure)
colorGrade 52   c*bri, then mix(luma, c, sat), then contrast (pinned 1.0)
tonemap 60      ACES(c * BASE_EXPOSURE * 2^expEV)
vignette 70     display-space multiply
demo_fade 200   letterbox + the part's fadeIn/fadeOut
asc_endcard_point 210
  • invAces is the exact inverse of the engine's Narkowicz operator.
  • unSaturate is the exact inverse of the colorGrade pass's saturation term.
  • Multiplicative exposure and brightness are cancelled by the dome's own exposure property, which carries 1 / aux.sceneExposure.

The dome's fragment shader applies all of that per pixel, so a §5 hex lands on itself whatever the grade and expEV do — including at the dark end, where feeding the plain linearised hex loses the palette outright (#060A12 arrives at 0.0018 and leaves the tonemap at 0.0004, a ninth of one 8-bit step).

Anything LIT rather than emissive takes hexL, not hexD. The seam is for surfaces whose §5 value is what the frame should carry; a lit surface's value is decided by the render.

The gates, and the measurement window for every number

verify(zenD, horD, skyCeil, vign, sunGlow, mid) replays the dome's own blend law and reports three detectors. asc_clock.checkPalette() runs it against the triples the dome actually receives at each timestamp — the §5 rows already folded toward the void by skyMix, the derived ceiling, and the dome's glow term.

detectordefinitionbudget
magentaPctgreen below both other channels, value ≥ 500.00 %
relSatPct(max−min)/max < 0.25, value ≥ 40≤ 2 %
absSatPct(max−min) ≤ 20, value ≥ 40≤ 2 %

The magenta gate is exactly zero because that family is exactly avoidable. The low-saturation gates are not zero and cannot be: some sample on any continuous ramp between §5's two ends sits near the neutral axis, and the only questions are how few and how dark.

The function's numbers are not the frame's numbers, and the frame's are the ones that count. Near the horizon a camera looking level compresses many screen rows into a small range of elevation, so the transition band costs several times the area a uniform sample over elevation suggests. Measured on the shipped frames (active frame only, letterbox excluded), the two detectors the round-1 critics used read:

round 1now
mid-tone neutral (value 96–159, sat < 0.10) — the brief's own test9.2–26.4 %0.00 % at all 16 timestamps
magenta family3.4–8.5 %0.00 % at all 16 timestamps
dark pixels not carrying blue (§5 rule 2)44–70 %0.00–0.56 %
any low-saturation pixel, value ≥ 6026.4 % at H40.00 % at 10 of 16; 1.3–4.6 % at 4; 10.5 % at t=05; 25.0 % at t=11

The last row is the transition band itself, at value 60–99 — dark olive-teal rows a few pixels deep, not the mid-tone #808080 the brief bans and not in the banned #C0–#E0 band. t = 05 and t = 11 are its worst two frames because skyMix is folding the whole gradient toward a cold void there, which compresses every channel difference at once.