Back to Safe Sand · Material studies
SOURCE / PINNED RELEASE

Made of little things.

Safe Sand · Material studies

Release
9101a9cd9fb4…
Author-recorded commit
75d98e1b8edb…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-7682e55a791

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

src/setup.ts
export interface Setup {
  version: 1;
  material: string;
  surface: 'light' | 'slate' | 'dark';
  radius: number;
  amount: number;
  tool?: string;
  speed?: number;
  pitch?: number;
  yaw?: number;
  flowRate?: number;
  pourHeight?: number;
}

export function parseSetup(raw: string, materialKeys: readonly string[]): Setup {
  const value: unknown = JSON.parse(raw);
  if (!value || typeof value !== 'object') throw new Error('Invalid saved setup.');
  const s = value as Record<string, unknown>;
  if (s.version !== 1 || typeof s.material !== 'string' || !materialKeys.includes(s.material)
    || !['light', 'slate', 'dark'].includes(String(s.surface))
    || typeof s.radius !== 'number' || !Number.isFinite(s.radius) || s.radius < 2 || s.radius > 10
    || ![0.35, 0.8, 1].includes(Number(s.amount)) || typeof s.amount !== 'number') {
    throw new Error('This saved setup is not compatible.');
  }
  const optional: Partial<Setup> = {};
  const ranges = { pitch: [-35, 35], yaw: [-35, 35], flowRate: [2000, 24000], pourHeight: [2, 30] };
  for (const [key, [min, max]] of Object.entries(ranges)) {
    if (s[key] === undefined) continue;
    if (typeof s[key] !== 'number' || !Number.isFinite(s[key]) || s[key] < min || s[key] > max) throw new Error('Invalid saved control value.');
    Object.assign(optional, { [key]: s[key] });
  }
  if (s.speed !== undefined) {
    if (typeof s.speed !== 'number' || ![0.25, 0.5, 1].includes(s.speed)) throw new Error('Invalid saved speed.');
    optional.speed = s.speed;
  }
  if (s.tool !== undefined) {
    if (typeof s.tool !== 'string' || !['push', 'pour', 'rake', 'erase', 'magnet', 'repel', 'camera'].includes(s.tool)) throw new Error('Invalid saved tool.');
    optional.tool = s.tool;
  }
  return { version: 1, material: s.material, surface: s.surface as Setup['surface'], radius: s.radius, amount: s.amount, ...optional };
}