Back to Pursuit PIT
SOURCE / PINNED RELEASE

Made of little things.

Pursuit PIT

Release
565f4981bdc3…
Author-recorded commit
197afdbed44b…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7

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

src/game/garage.ts
// The garage: how the player's patrol car and getaway car look. The renderer builds the cars
// from this; the sim takes the getaway car's body (its size and weight) and paint.
import { VEHICLES, type Car, type VehicleType } from './sim.js';

export interface Livery { name: string; body: number; doors: number; stripe?: number }
export interface Choice { name: string; color: number }

/** Patrol car paint schemes. Police cars in a getaway wear the same one. */
export const LIVERIES: Livery[] = [
  { name: 'Black & white', body: 0x1d2128, doors: 0xf4f4f4 },
  { name: 'All white', body: 0xeceef0, doors: 0xeceef0, stripe: 0x1d3f8a },
  { name: 'Blue & white', body: 0x1d3f8a, doors: 0xf4f4f4 },
  { name: 'Stealth black', body: 0x15171a, doors: 0x15171a, stripe: 0x2b2e33 },
  { name: 'State trooper', body: 0xb9a37a, doors: 0x3a2f28, stripe: 0x3a2f28 },
];

/** Light bar colours: the two halves flash in turn. */
export const LIGHT_BARS: { name: string; a: number; b: number }[] = [
  { name: 'Red & blue', a: 0xff3030, b: 0x3070ff },
  { name: 'All blue', a: 0x3070ff, b: 0x3a8cff },
  { name: 'Red & amber', a: 0xff3030, b: 0xffa020 },
  { name: 'Red & white', a: 0xff3030, b: 0xf2f4ff },
];

export const WHEELS: Choice[] = [
  { name: 'Black steel', color: 0x2c2d31 },
  { name: 'Chrome', color: 0xd5d9de },
  { name: 'Gunmetal', color: 0x55595f },
  { name: 'Gold', color: 0xc9a227 },
];

/** Getaway bodies. They differ in size and weight; speed and grip stay the same. */
export const BODIES: { type: VehicleType; name: string; note: string }[] = [
  { type: 'sedan', name: 'Sedan', note: 'Balanced: easy to thread through traffic.' },
  { type: 'sports', name: 'Sports car', note: 'Smallest and lightest: slips through gaps, but gets shoved around.' },
  { type: 'pickup', name: 'Pickup', note: 'Longer and heavier: shoulders smaller cars aside.' },
  { type: 'lifted', name: 'Lifted truck', note: 'Heaviest of all: hard for the police to PIT.' },
];

export const PAINTS: Choice[] = [
  { name: 'Orange', color: 0xf28a1a },
  { name: 'Red', color: 0xc0262b },
  { name: 'Yellow', color: 0xe8c22a },
  { name: 'Lime', color: 0x7cc23a },
  { name: 'Green', color: 0x2f6b45 },
  { name: 'Teal', color: 0x1f8a8a },
  { name: 'Blue', color: 0x2f5fb8 },
  { name: 'Purple', color: 0x6a3fa0 },
  { name: 'Pink', color: 0xd9558f },
  { name: 'White', color: 0xe6e6e0 },
  { name: 'Silver', color: 0x9aa1a8 },
  { name: 'Black', color: 0x232428 },
];

export interface GarageSpec {
  police: { livery: number; lights: number; wheels: number; pushBar: boolean };
  getaway: { body: number; paint: number; wheels: number };
}

export const DEFAULT_GARAGE: GarageSpec = {
  police: { livery: 0, lights: 0, wheels: 0, pushBar: true },
  getaway: { body: 0, paint: 0, wheels: 1 },
};

const clampIndex = (v: unknown, list: unknown[], fallback: number) =>
  Number.isInteger(v) && (v as number) >= 0 && (v as number) < list.length ? (v as number) : fallback;

/** Read a saved garage, falling back to defaults for anything missing or out of range. */
export function parseGarage(raw: string | null): GarageSpec {
  try {
    const g = raw ? JSON.parse(raw) : {};
    const p = g.police ?? {}, w = g.getaway ?? {};
    return {
      police: {
        livery: clampIndex(p.livery, LIVERIES, 0),
        lights: clampIndex(p.lights, LIGHT_BARS, 0),
        wheels: clampIndex(p.wheels, WHEELS, 0),
        pushBar: p.pushBar !== false,
      },
      getaway: {
        body: clampIndex(w.body, BODIES, 0),
        paint: clampIndex(w.paint, PAINTS, 0),
        wheels: clampIndex(w.wheels, WHEELS, 1),
      },
    };
  } catch {
    return structuredClone(DEFAULT_GARAGE);
  }
}

/** Put the chosen getaway body and paint on the player's car. */
export function fitGetawayCar(car: Car, spec: GarageSpec): void {
  const type = BODIES[spec.getaway.body].type;
  const v = VEHICLES[type];
  car.type = type;
  car.len = v.len;
  car.wid = v.wid;
  car.mass = v.mass;
  car.inertia = (v.mass * (v.len * v.len + v.wid * v.wid)) / 12;
  car.color = PAINTS[spec.getaway.paint].color;
  // A pickup's bed is open where a sedan's trunk lid would be.
  car.lost = type === 'pickup' || type === 'lifted' ? 1 << 3 : 0;
}