clone

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

main
marbles / src / shared / classify.ts
classify.ts 2.6K
1/**2 * Filename → piece classification. Single source of truth for Marble Kit3 * piece semantics, shared by the asset pipeline, the piece registry, and the4 * generation systems. Token-based so it extends to other Kenney packs.5 */6import type { PieceClassification } from "./types.ts";78/** Marble Kit families the classifier recognizes as track/decor semantics. */9export const MARBLE_FAMILIES = [10 "straight",11 "tunnel",12 "cross",13 "corner",14 "end",15 "curve",16 "bend",17 "s-curve",18 "helix",19 "split",20 "ramp",21 "slant",22 "bump",23 "wave",24 "funnel",25 "fan",26 "support",27 "marble",28 "banner",29 "tree",30] as const;3132export type MarbleFamily = (typeof MARBLE_FAMILIES)[number];3334const FAMILY_SET = new Set<string>(MARBLE_FAMILIES);3536/**37 * Classify a piece by its GLB base name (e.g. "s-curve-short-left-a").38 * Returns family "unknown" when the leading token isn't recognized —39 * callers decide whether that is fatal (Marble Kit) or generic decor.40 */41export function classify(id: string): PieceClassification {42 const tokens = id.split("-");4344 // "s-curve-*" is the one family whose name contains a dash.45 let family: string;46 let rest: string[];47 if (tokens[0] === "s" && tokens[1] === "curve") {48 family = "s-curve";49 rest = tokens.slice(2);50 } else if (FAMILY_SET.has(tokens[0])) {51 family = tokens[0];52 rest = tokens.slice(1);53 } else {54 family = "unknown";55 rest = tokens.slice(1);56 }5758 const has = (t: string) => rest.includes(t);5960 const tier = (["a", "b", "c", "d"] as const).find((t) => has(t)) ?? null;6162 let span: PieceClassification["span"] = null;63 if (family === "helix") {64 span = has("quarter") ? "quarter" : has("half") ? "half" : "full";65 }6667 let segment: PieceClassification["segment"] = null;68 if (family === "support") {69 if (has("top") && has("double")) segment = "top-double";70 else if (has("top")) segment = "top";71 else if (has("middle")) segment = "middle";72 else if (has("bottom")) segment = "bottom";73 }7475 return {76 family,77 solid: has("solid"),78 hole: has("hole"),79 wide: has("wide"),80 long: has("long"),81 short: has("short"),82 // support "top-double" is a stack segment, not a doubled piece83 double: has("double") && segment === null,84 color: has("color"),85 high: has("high"),86 low: has("low"),87 tier,88 size: has("large") ? "large" : has("medium") ? "medium" : null,89 chirality: has("left") ? "left" : has("right") ? "right" : null,90 span,91 count: has("single")92 ? "single"93 : has("two")94 ? "two"95 : has("four")96 ? "four"97 : null,98 segment,99 };100}