see /admin/mapping page.

This commit is contained in:
2025-12-07 09:57:46 -05:00
parent caf4817d12
commit 6cf3ea7c94
4 changed files with 746 additions and 3 deletions
+263
View File
@@ -0,0 +1,263 @@
// 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<CategoryId, unknown>
): 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, unknown>
): CategoryId[] | null {
for (const pattern of slot.satisfactionPatterns) {
const ok = pattern.every((categoryId) =>
Boolean(selectedByCategory[categoryId])
);
if (ok) return pattern;
}
return null;
}