// data/builderSlots.ts /** * Builder "slots" represent the major assemblies in a Battl Builder rifle: * - Lower Receiver Assembly * - Upper Receiver Assembly * - Fire Control / Optics / Accessories, etc. * * Each slot can be satisfied by one or more "patterns" of categories. * Example: * Lower Assembly is satisfied by EITHER: * - a single Complete Lower * - OR a combination of stripped lower + parts + buffer + stock + grip + safety * * The frontend can use this file to: * - show build progress for each slot * - grey out / collapse sub-categories when a complete assembly is selected * - validate that a build is "complete" before sharing/saving */ export type SlotGroup = "LOWER" | "UPPER" | "SYSTEM" | "ACCESSORIES"; /** * CategoryId should correspond to CATEGORIES[id] from data/gunbuilderParts.ts * We keep it loose here to avoid circular type imports. */ export type CategoryId = string; export type BuilderSlotId = | "lowerAssembly" | "upperAssembly" | "opticSystem" | "sightSystem" | "suppressorSystem" | "magazineSystem" | "accessoriesSupport"; export interface BuilderSlot { id: BuilderSlotId; label: string; group: SlotGroup; /** * Human-facing description for tooltips / help text. */ description: string; /** * All category IDs that are visually associated with this slot. * (Useful for layout / grouping in the UI.) */ categories: CategoryId[]; /** * Each inner array is a valid way to "satisfy" the slot. * * Example (lowerAssembly): * [ * ["completeLower"], // Pattern 1: single complete lower * ["lower", "lowerParts", "trigger", "grip", "safety", "buffer", "stock"] // Pattern 2: custom lower * ] */ satisfactionPatterns: CategoryId[][]; } /** * Core Battl Builder slots. * * These line up with your existing CATEGORIES in data/gunbuilderParts.ts: * * LOWER: * - lower * - completeLower * - lowerParts * - trigger * - grip * - safety * - buffer * - stock * * UPPER: * - upper * - completeUpper * - bcg * - barrel * - gasBlock * - gasTube * - muzzleDevice * - suppressor * - handguard * - chargingHandle * - sights * - optic * * ACCESSORIES: * - magazine * - weaponLight * - foregrip * - bipod * - sling * - railAccessory * - tools */ export const BUILDER_SLOTS: BuilderSlot[] = [ { id: "lowerAssembly", label: "Lower Receiver Assembly", group: "LOWER", description: "The serialized foundation of the rifle: receiver, controls, fire control, stock, and buffer system.", categories: [ "lower", "completeLower", "lowerParts", "trigger", "grip", "safety", "buffer", "stock", ], satisfactionPatterns: [ // Pattern 1: single complete lower does it all ["completeLower"], // Pattern 2: fully custom lower built from individual parts ["lower", "lowerParts", "trigger", "grip", "safety", "buffer", "stock"], ], }, { id: "upperAssembly", label: "Upper Receiver Assembly", group: "UPPER", description: "The working end of the rifle: upper, barrel, gas system, handguard, BCG, charging handle, and muzzle device.", categories: [ "upper", "completeUpper", "bcg", "barrel", "gasBlock", "gasTube", "handguard", "muzzleDevice", "chargingHandle", ], satisfactionPatterns: [ // Pattern 1: complete upper ["completeUpper"], // Pattern 2: custom upper from parts [ "upper", "barrel", "gasBlock", "gasTube", "handguard", "bcg", "chargingHandle", "muzzleDevice", ], ], }, { id: "opticSystem", label: "Optic System", group: "SYSTEM", description: "Primary glass for the rifle: LPVOs, red dots, prisms, and magnifiers.", categories: ["optic"], satisfactionPatterns: [ // For now: any optic fills the slot. ["optic"], ], }, { id: "sightSystem", label: "Backup / Iron Sights", group: "SYSTEM", description: "Backup irons or primary sights for no-glass builds and hard-use setups.", categories: ["sights"], satisfactionPatterns: [["sights"]], }, { id: "suppressorSystem", label: "Muzzle / Suppressor", group: "SYSTEM", description: "Dedicated suppressor slot for QD or direct-thread cans. (Muzzle device is handled in the upper assembly patterns.)", categories: ["suppressor"], satisfactionPatterns: [["suppressor"]], }, { id: "magazineSystem", label: "Magazines", group: "ACCESSORIES", description: "Feeding the beast: mags appropriate to the platform and caliber.", categories: ["magazine"], satisfactionPatterns: [["magazine"]], }, { id: "accessoriesSupport", label: "Support Gear", group: "ACCESSORIES", description: "Lights, lasers, foregrips, bipods, slings, rail widgets, and tools that keep the rifle honest.", categories: [ "weaponLight", "foregrip", "bipod", "sling", "railAccessory", "tools", ], // Accessories are all optional; this slot is never strictly "required" satisfactionPatterns: [ // An empty pattern would technically always be satisfied, // but we can treat this slot as "nice to have" in the UI instead. ["weaponLight"], ["foregrip"], ["bipod"], ["sling"], ["railAccessory"], ["tools"], ], }, ]; /** * Helper: determine whether a slot is satisfied given currently selected parts. * * `selectedByCategory` is expected to be a map of categoryId → truthy if selected. * The value can be the actual Part, or just `true` if you track it elsewhere. */ export function isSlotSatisfied( slot: BuilderSlot, selectedByCategory: Record ): boolean { return slot.satisfactionPatterns.some((pattern) => pattern.every((categoryId) => Boolean(selectedByCategory[categoryId])) ); } /** * Optional helper: get the first pattern that is currently satisfied. * Can be used to: * - show which "path" the user took (complete vs custom) * - conditionally collapse/expand sub-categories in the UI */ export function getSatisfiedPattern( slot: BuilderSlot, selectedByCategory: Record ): CategoryId[] | null { for (const pattern of slot.satisfactionPatterns) { const ok = pattern.every((categoryId) => Boolean(selectedByCategory[categoryId]) ); if (ok) return pattern; } return null; }