Back to Supersonic RC Revive
SOURCE / PINNED RELEASE

Made of little things.

Supersonic RC Revive

Release
372375d18e3f…
Author-recorded commit
6fbf09f88a0a…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f

Archive hash verified: 76e4eb30c67a6f45…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tools/assets/textures.ts
// Procedural textures for the custom level (RGBA8, sRGB). Kept small and
// low-noise so the PNGs stay compact; the baked lighting adds the depth.
import { writePng } from './files.ts';

export interface TextureOut {
  name: string;
  png: Buffer;
}

/** Deterministic PRNG (mulberry32). */
export function rng(seed: number): () => number {
  let a = seed >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function image(w: number, h: number, f: (x: number, y: number) => [number, number, number]): Uint8Array {
  const out = new Uint8Array(w * h * 4);
  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      const [r, g, b] = f(x, y);
      const i = (y * w + x) * 4;
      out[i] = Math.max(0, Math.min(255, Math.round(r)));
      out[i + 1] = Math.max(0, Math.min(255, Math.round(g)));
      out[i + 2] = Math.max(0, Math.min(255, Math.round(b)));
      out[i + 3] = 255;
    }
  }
  return out;
}

/** Four staggered planks across, honey-coloured, with soft grain and dark seams. */
function planks(): Uint8Array {
  const W = 128;
  const H = 256;
  const r = rng(7);
  const tones = Array.from({ length: 8 }, () => 0.9 + r() * 0.2);
  const grainPhase = Array.from({ length: 8 }, () => r() * 6.28);
  return image(W, H, (x, y) => {
    const plank = Math.floor(x / 32);
    const offset = (plank % 2) * 128 + plank * 37;
    const yy = (y + offset) % H;
    const seg = plank * 2 + (yy < 128 ? 0 : 1);
    const t = tones[seg];
    const seamX = x % 32 === 0 || x % 32 === 31;
    const seamY = yy === 0 || yy === 128;
    const grain = 0.05 * Math.sin(yy * 0.21 + Math.sin((x % 32) * 0.4 + grainPhase[seg]) * 2.5);
    const k = (seamX || seamY ? 0.62 : 1) * (t + grain);
    return [222 * k, 168 * k, 112 * k];
  });
}

/** A round-cornered rug: border bands and a diamond field. */
function rug(): Uint8Array {
  const S = 128;
  return image(S, S, (x, y) => {
    const u = (x + 0.5) / S;
    const v = (y + 0.5) / S;
    const edge = Math.min(u, v, 1 - u, 1 - v);
    if (edge < 0.06) return [44, 72, 128];
    if (edge < 0.1) return [240, 214, 120];
    if (edge < 0.14) return [44, 72, 128];
    const d = Math.abs(((u * 6) % 1) - 0.5) + Math.abs(((v * 6) % 1) - 0.5);
    return d < 0.3 ? [214, 88, 70] : [236, 226, 204];
  });
}

/** Start/finish checker. */
function checker(): Uint8Array {
  return image(32, 32, (x, y) => ((Math.floor(x / 8) + Math.floor(y / 8)) % 2 ? [24, 24, 28] : [244, 244, 240]));
}

/** Hazard stripes for ramp lips and kickers. */
function stripes(): Uint8Array {
  return image(64, 64, (x, y) => ((x + y) % 32 < 16 ? [255, 196, 32] : [36, 36, 40]));
}

/** The blob shadow under the car (ui/carshadow): black, soft-edged alpha. */
export function carShadow(): Buffer {
  const W = 32;
  const H = 64;
  const out = new Uint8Array(W * H * 4);
  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      // Distance outside a rounded rectangle, in normalised units.
      const dx = Math.max(0, Math.abs((x + 0.5) / W - 0.5) * 2 - 0.45) / 0.55;
      const dy = Math.max(0, Math.abs((y + 0.5) / H - 0.5) * 2 - 0.7) / 0.3;
      const d = Math.min(1, Math.hypot(dx, dy));
      out[(y * W + x) * 4 + 3] = Math.round(255 * (1 - d * d * (3 - 2 * d)));
    }
  }
  return writePng(W, H, out);
}

export function buildTextures(): TextureOut[] {
  return [
    { name: 'planks', png: writePng(128, 256, planks()) },
    { name: 'rug', png: writePng(128, 128, rug()) },
    { name: 'checker', png: writePng(32, 32, checker()) },
    { name: 'stripes', png: writePng(64, 64, stripes()) },
  ];
}