clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

main
marbles / src / client / pipeColor.ts
pipeColor.ts 1.8K
1/**2 * Per-seed pipe color (user request): the track's plastic body color is a3 * variation axis, not a fixed kit palette. Drawn from its own RNG family4 * (`pipe-color`) so it's deterministic per seed and touches NOTHING in5 * the generator — placements, goldens, and genVersion are unaffected; the6 * recolor happens at the texture level in the client (assets.ts swaps the7 * pipe swatch texels of the active colormap).8 */9import { rngFamily } from "../gen/rng.ts";1011export type Rgb = readonly [number, number, number];1213/** Pipe body color, or null to keep the kit's stock swatch. Loop mode14 * draws once per seed; endless draws PER ISLAND CHAPTER (user rule: the15 * pipe color is per island — the line launched from an island carries16 * its color until the next anchor). Pastel-to-rich range: saturation and17 * lightness bounded so the channel stays readable against every18 * atmosphere. */19export function drawPipeColor(20 seed: string,21 chapter: number | null = null,22): Rgb | null {23 if (chapter !== null && chapter < 0) return null; // before the home island24 const rng = rngFamily(25 seed,26 chapter === null ? "pipe-color" : `pipe-color/${chapter}`,27 );28 if (rng.chance(0.22)) return null; // classic kit look29 const h = rng.float();30 const s = 0.35 + rng.float() * 0.4;31 const l = 0.45 + rng.float() * 0.22;32 return hslToRgb(h, s, l);33}3435function hslToRgb(h: number, s: number, l: number): Rgb {36 const q = l < 0.5 ? l * (1 + s) : l + s - l * s;37 const p = 2 * l - q;38 const channel = (t: number): number => {39 if (t < 0) t += 1;40 if (t > 1) t -= 1;41 if (t < 1 / 6) return p + (q - p) * 6 * t;42 if (t < 1 / 2) return q;43 if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;44 return p;45 };46 return [47 Math.round(channel(h + 1 / 3) * 255),48 Math.round(channel(h) * 255),49 Math.round(channel(h - 1 / 3) * 255),50 ];51}