var CrewChiefReport = (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // scripts/lib/experimental/browser-entry.mjs var browser_entry_exports = {}; __export(browser_entry_exports, { ADVANCED_CHASSIS_CAVEAT: () => ADVANCED_CHASSIS_CAVEAT, ADVANCED_COACH_HEADER: () => ADVANCED_COACH_HEADER, ADVANCED_GEOMETRY_SOURCE: () => ADVANCED_GEOMETRY_SOURCE, ADVANCED_LOGGING_SOURCE: () => ADVANCED_LOGGING_SOURCE, ADVANCED_SOURCE: () => ADVANCED_SOURCE, CAPTURE_OVERRIDE_STORE_KEY: () => CAPTURE_OVERRIDE_STORE_KEY, CAPTURE_TIER_LABELS: () => CAPTURE_TIER_LABELS, CLASS_VARIANTS: () => CLASS_VARIANTS, DELIVERY_STATUS: () => DELIVERY_STATUS, FOCUSED_MODIFICATION_OPTIONS: () => FOCUSED_MODIFICATION_OPTIONS, FOCUSED_REF_DEFS: () => FOCUSED_REF_DEFS, HORIZONTAL_THIRD_DAMPER_PRIORS: () => HORIZONTAL_THIRD_DAMPER_PRIORS, LM_MOD_MULTI_SHOCK_OPTIONS: () => LM_MOD_MULTI_SHOCK_OPTIONS, LM_MOD_OPTIONAL_PICKUPS: () => LM_MOD_OPTIONAL_PICKUPS, LM_MOD_PLATFORM_MODS: () => LM_MOD_PLATFORM_MODS, LM_MOD_PROFILES: () => LM_MOD_PROFILES, LM_MOD_REQUIRED_PICKUPS: () => LM_MOD_REQUIRED_PICKUPS, LM_MOD_TRAILING_ARM_STYLE: () => LM_MOD_TRAILING_ARM_STYLE, MODIFICATION_OPTIONS: () => MODIFICATION_OPTIONS, MULTI_SHOCK_MATCH_TERMS: () => MULTI_SHOCK_MATCH_TERMS, PIVOT_TIER: () => PIVOT_TIER, PIVOT_TIER_LABELS: () => PIVOT_TIER_LABELS, REC_TIER: () => REC_TIER, SNAP_PRESETS: () => SNAP_PRESETS, THIN_DATA_CAVEAT: () => THIN_DATA_CAVEAT, TRACK_SNAP_STORAGE_KEY: () => TRACK_SNAP_STORAGE_KEY, advancedChassisLoggingPriors: () => advancedChassisLoggingPriors, advancedRecTag: () => advancedRecTag, advancedSprintGeometryPriors: () => advancedSprintGeometryPriors, advancedTrackEvolutionPriors: () => advancedTrackEvolutionPriors, advancedTryFirstRecTag: () => advancedTryFirstRecTag, advancedWingGeometryPriors: () => advancedWingGeometryPriors, analyzeSessionTrend: () => analyzeSessionTrend, applyAdvancedPrioritization: () => applyAdvancedPrioritization, assessAdvancedContext: () => assessAdvancedContext, assessDataCaptureState: () => assessDataCaptureState, assessFocusedPivotCompleteness: () => assessFocusedPivotCompleteness, assessGrassrootsContext: () => assessGrassrootsContext, assessPivotCompleteness: () => assessPivotCompleteness, buildAdvancedCarSummaryLine: () => buildAdvancedCarSummaryLine, buildAdvancedChassisCoachLine: () => buildAdvancedChassisCoachLine, buildAdvancedContextCoachPayload: () => buildAdvancedContextCoachPayload, buildAdvancedContextGapLine: () => buildAdvancedContextGapLine, buildAdvancedDataCaptureCoachPayload: () => buildAdvancedDataCaptureCoachPayload, buildAdvancedDataNote: () => buildAdvancedDataNote, buildAdvancedDataQualityNote: () => buildAdvancedDataQualityNote, buildAdvancedPrefill: () => buildAdvancedPrefill, buildCapturePriorities: () => buildCapturePriorities, buildClassVariantCoachLine: () => buildClassVariantCoachLine, buildEvolutionContext: () => buildEvolutionContext, buildGrassrootsCarSummaryLine: () => buildGrassrootsCarSummaryLine, buildGrassrootsContextGapLine: () => buildGrassrootsContextGapLine, buildMultiSessionPlanning: () => buildMultiSessionPlanning, buildPivotCoachPayload: () => buildPivotCoachPayload, buildPlatformIntegritySave: () => buildPlatformIntegritySave, buildPostCaptureFeedback: () => buildPostCaptureFeedback, buildQuickLogCaptureHint: () => buildQuickLogCaptureHint, buildSmartCaptureDefaults: () => buildSmartCaptureDefaults, clearPickupStaleFlag: () => clearPickupStaleFlag, computeCaptureTier: () => computeCaptureTier, crewChiefReport: () => crewChiefReport, deriveInstrumentation: () => deriveInstrumentation, detectAdvancedChassisManufacturer: () => detectAdvancedChassisManufacturer, detectAdvancedFeelSymptom: () => detectAdvancedFeelSymptom, diffCaptureOverrides: () => diffCaptureOverrides, driverBuilderPhilosophyCore: () => driverBuilderPhilosophyCore, enrichAdvancedVehicleContext: () => enrichAdvancedVehicleContext, evaluatePivotFreshness: () => evaluatePivotFreshness, expectedPickupKeysForCar: () => expectedPickupKeysForCar, feelChipToTrait: () => feelChipToTrait, formatRecommendationTier: () => formatRecommendationTier, formatTrackConditionSummary: () => formatTrackConditionSummary, hasDeepChassisKnowledge: () => hasDeepChassisKnowledge, hasMultiShockRear: () => hasMultiShockRear, hasRepeatableRow: () => hasRepeatableRow, isAdvancedChassisProfile: () => isAdvancedChassisProfile, isAdvancedDeepBuilder: () => isAdvancedDeepBuilder, isAscsSportsmanVariant: () => isAscsSportsmanVariant, isGrassrootsDayOneProfile: () => isGrassrootsDayOneProfile, isJuniorSprintVariant: () => isJuniorSprintVariant, isLmModPivotClass: () => isLmModPivotClass, isMicro270Variant: () => isMicro270Variant, isMicro600Variant: () => isMicro600Variant, isMicroTurfVariant: () => isMicroTurfVariant, isModLiteVariant: () => isModLiteVariant, isSeriousCaptureProfile: () => isSeriousCaptureProfile, isThinDataChassisCase: () => isThinDataChassisCase, lmModEvolutionAdjustments: () => lmModEvolutionAdjustments, lmModModernSetupPriors: () => lmModModernSetupPriors, markGeometryPickupsStale: () => markGeometryPickupsStale, modificationsShouldStalePickups: () => modificationsShouldStalePickups, multiShockDamperPriors: () => multiShockDamperPriors, normalizePlatformIntegrity: () => normalizePlatformIntegrity, normalizeTrackContext: () => normalizeTrackContext, normalizeTrackSnap: () => normalizeTrackSnap, normalizeTrackStateBucket: () => normalizeTrackStateBucket, pickAdvancedGeometryBriefLine: () => pickAdvancedGeometryBriefLine, pickAdvancedLoggingBriefLine: () => pickAdvancedLoggingBriefLine, pickAdvancedTrackEvolutionBriefLine: () => pickAdvancedTrackEvolutionBriefLine, pickLmModPivotBriefLine: () => pickLmModPivotBriefLine, pickMach1DriverBuilderBrief: () => pickMach1DriverBuilderBrief, pickMultiShockBriefLine: () => pickMultiShockBriefLine, pickThinDataChassisBrief: () => pickThinDataChassisBrief, pickTrackSnapBriefLine: () => pickTrackSnapBriefLine, pivotTierFeatures: () => pivotTierFeatures, prefillFromHistory: () => prefillFromHistory, recTag: () => recTag, recommend: () => recommend, recomputeOverrideBias: () => recomputeOverrideBias, recordCaptureOverride: () => recordCaptureOverride, renderCrewChief: () => renderCrewChief, renderMarkdown: () => renderMarkdown, renderRecommendations: () => renderRecommendations, reportJSON: () => reportJSON, resolveAdvancedChassisDeepCaveat: () => resolveAdvancedChassisDeepCaveat, resolveAdvancedChassisHint: () => resolveAdvancedChassisHint, resolveBaselineProfileFromVariant: () => resolveBaselineProfileFromVariant, resolveCaptureProfile: () => resolveCaptureProfile, resolveClassVariant: () => resolveClassVariant, resolveGeometryTrustMode: () => resolveGeometryTrustMode, resolvePivotCarType: () => resolvePivotCarType, resolvePivotGrassrootsProfile: () => resolvePivotGrassrootsProfile, resolvePivotTier: () => resolvePivotTier, rowToRecord: () => rowToRecord, rowToRepeatableForm: () => rowToRepeatableForm, rowsToRecords: () => rowsToRecords, smartDefaultsToForm: () => smartDefaultsToForm, suggestNextSessionLabel: () => suggestNextSessionLabel, thinDataChassisPriors: () => thinDataChassisPriors, traitToFeelChip: () => traitToFeelChip }); // scripts/lib/experimental/groovingAnalysis.mjs var mean = (a) => a.reduce((x, y) => x + y, 0) / a.length; var sd = (a) => { const m = mean(a); return Math.sqrt(a.reduce((s, x) => s + (x - m) ** 2, 0) / Math.max(1, a.length - 1)); }; var round = (x, n) => { const p = 10 ** n; return Math.round(x * p) / p; }; function effect(rows, treatmentFn, responseFn, thr = null) { const tv = rows.map(treatmentFn).filter((t) => t != null && typeof t === "number" && isFinite(t)); const cut = thr != null ? thr : tv.length ? median(tv) : null; if (cut == null) return { n_lo: 0, n_hi: 0, verdict: "insufficient-n" }; const lo = [], hi = []; for (const r of rows) { const t = treatmentFn(r), y = responseFn(r); if (y == null || t == null) continue; (t <= cut ? lo : hi).push(y); } if (lo.length < 5 || hi.length < 5) return { n_lo: lo.length, n_hi: hi.length, verdict: "insufficient-n" }; const ml = mean(lo), mh = mean(hi), delta = mh - ml; const sp = Math.sqrt(((lo.length - 1) * sd(lo) ** 2 + (hi.length - 1) * sd(hi) ** 2) / (lo.length + hi.length - 2)); if (!(sp > 1e-9)) return { n_lo: lo.length, n_hi: hi.length, mean_lo: round(ml, 3), mean_hi: round(mh, 3), delta: round(delta, 3), cohen_d: null, z: null, verdict: Math.abs(delta) < 1e-9 ? "null/noise" : "insufficient-variance" }; const d = delta / sp; const se = sp * Math.sqrt(1 / lo.length + 1 / hi.length); const z = se ? delta / se : 0; let verdict; const az = Math.abs(z), ad = Math.abs(d); if (az >= 2.5 && ad >= 0.3) verdict = "SIGNAL"; else if (az < 1.5 || ad < 0.2) verdict = "null/noise"; else verdict = "weak"; return { n_lo: lo.length, n_hi: hi.length, mean_lo: round(ml, 3), mean_hi: round(mh, 3), delta: round(delta, 3), cohen_d: round(d, 2), z: round(z, 2), verdict }; } function stratified(rows, treatmentFn, responseFn, stratumFn) { const groups = {}; for (const r of rows) { const s = stratumFn(r); (groups[s] = groups[s] || []).push(r); } const out = {}; for (const k of Object.keys(groups)) out[k] = effect(groups[k], treatmentFn, responseFn); return out; } function confound(rows, treatmentFn, covFn) { const xs = [], ys = []; for (const r of rows) { const x = treatmentFn(r), y = covFn(r); if (x != null && y != null) { xs.push(x); ys.push(y); } } if (xs.length < 2) return 0; const mx = mean(xs), my = mean(ys); let c = 0, vx = 0, vy = 0; for (let i = 0; i < xs.length; i++) { c += (xs[i] - mx) * (ys[i] - my); vx += (xs[i] - mx) ** 2; vy += (ys[i] - my) ** 2; } return round(vx && vy ? c / Math.sqrt(vx * vy) : 0, 2); } function median(a) { const s = [...a].sort((x, y) => x - y); const m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; } // scripts/lib/experimental/studies.mjs var grip = (st) => ({ heavy: 0.8, tacky: 0.9, slick: 0.4, dry_slick: 0.15 })[st]; var rough = (st) => ({ heavy: 0.6, tacky: 0.2, slick: 0.45, dry_slick: 0.8 })[st]; var S = (r) => r.setup; var Rp = (r) => r.responses; var isRough = (r) => ["slick", "dry_slick", "heavy"].includes(r.track_state); var STUDIES = [ // grooving / siping { domain: "grooving", id: "G1", label: "siping -> heat-up (cold/hard tires)", expect: "+", treatment: (r) => S(r).sipe_count, response: (r) => Rp(r).heat_up_rate, filter: (r) => r.surface_temp_f <= 85 }, { domain: "grooving", id: "G2", label: "grooving -> lateral loss (REVERSES tacky<->dry-slick)", expect: "reversal", lore: true, treatment: (r) => S(r).groove_density, response: (r) => Rp(r).lateral_loss, stratifyBy: (r) => r.track_state, confoundVar: (r) => grip(r.track_state) }, // bump rubber / helper spring { domain: "bump", id: "B1", label: "bump engagement -> ADDS wheel hop on rough (challenge 'control')", expect: "+", lore: true, treatment: (r) => S(r).bump_engaged, response: (r) => Rp(r).wheel_hop_energy, filter: isRough, confoundVar: (r) => rough(r.track_state) }, { domain: "bump", id: "B2", label: "bump engagement -> less bottoming", expect: "-", treatment: (r) => S(r).bump_engaged, response: (r) => Rp(r).bottoming_rate }, { domain: "bump", id: "H1", label: "soft helper springs -> less wheel hop on rough", expect: "-", treatment: (r) => S(r).helper_soft, response: (r) => Rp(r).wheel_hop_energy, filter: isRough }, // panhard / J-bar { domain: "panhard", id: "P1", label: "panhard height -> lateral loss (REVERSES; challenge 'raise=bite')", expect: "reversal", lore: true, treatment: (r) => S(r).panhard_height, response: (r) => Rp(r).lateral_loss, stratifyBy: (r) => r.track_state, confoundVar: (r) => 1 - grip(r.track_state) }, { domain: "panhard", id: "P2", label: "panhard height -> wheel hop on rough (challenge 'higher=stable')", expect: "+", lore: true, treatment: (r) => S(r).panhard_height, response: (r) => Rp(r).wheel_hop_energy, filter: isRough, confoundVar: (r) => rough(r.track_state) }, // shock valving { domain: "shock", id: "S1", label: "HS compression -> wheel hop on rough (challenge 'stiffer=stable')", expect: "+", lore: true, treatment: (r) => S(r).comp_high, response: (r) => Rp(r).wheel_hop_energy, filter: isRough, confoundVar: (r) => rough(r.track_state) }, { domain: "shock", id: "S2", label: "compression -> less bottoming (confirms half the mnemonic)", expect: "-", treatment: (r) => (S(r).comp_low + S(r).comp_high) / 2, response: (r) => Rp(r).bottoming_rate }, { domain: "shock", id: "S3", label: "HS REBOUND -> wheel hop on rough (mnemonic says this should dominate)", expect: "+", treatment: (r) => S(r).reb_high, response: (r) => Rp(r).wheel_hop_energy, filter: isRough, compare_to: "S1" /* the mnemonic-breaker: compare d vs S1 */ }, { domain: "shock", id: "S4", label: "HS rebound -> forward drive on rough (challenge 'more rebound=drive')", expect: "-", lore: true, treatment: (r) => S(r).reb_high, response: (r) => Rp(r).forward_drive_index, filter: isRough }, // null control { domain: "control", id: "N1", label: "NULL: noise label -> lateral loss (should be null)", expect: "null", treatment: (r) => S(r).noise_label === "x" ? 1 : 0, response: (r) => Rp(r).lateral_loss, filter: (r) => ["x", "y"].includes(S(r).noise_label), threshold: 0.5 } ]; // scripts/lib/experimental/metaHarness.mjs function invNorm(p) { const a = [-39.69683028665376, 220.9460984245205, -275.9285104469687, 138.357751867269, -30.66479806614716, 2.506628277459239]; const b = [-54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572]; const c = [-0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783]; const d = [0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416]; const pl = 0.02425; if (p < pl) { const q2 = Math.sqrt(-2 * Math.log(p)); return (((((c[0] * q2 + c[1]) * q2 + c[2]) * q2 + c[3]) * q2 + c[4]) * q2 + c[5]) / ((((d[0] * q2 + d[1]) * q2 + d[2]) * q2 + d[3]) * q2 + 1); } if (p <= 1 - pl) { const q2 = p - 0.5, r = q2 * q2; return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q2 / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1); } const q = Math.sqrt(-2 * Math.log(1 - p)); return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1); } function runStudies(corpus, studies = STUDIES) { const rows = []; const tagged = corpus.some((r) => r && r._provenance && typeof r._provenance.study_targets === "string"); const targets = (r, id) => r._provenance && typeof r._provenance.study_targets === "string" && r._provenance.study_targets.split(/[ ,]+/).includes(id); for (const st of studies) { let base = st.filter ? corpus.filter(st.filter) : corpus; if (tagged) { const scoped = base.filter((r) => targets(r, st.id)); if (scoped.length) base = scoped; } if (st.stratifyBy) { const strata = stratified(base, st.treatment, st.response, st.stratifyBy); for (const [k, e] of Object.entries(strata)) rows.push({ ...meta(st), test: `${st.id}:${k}`, stratum: k, ...flat(e) }); } else { rows.push({ ...meta(st), test: st.id, ...flat(effect(base, st.treatment, st.response, st.threshold ?? null)) }); } if (st.confoundVar) st._confound = confound(base, st.treatment, st.confoundVar); } const m = rows.filter((r) => r.z != null).length; const z_adj = invNorm(1 - 0.05 / (2 * Math.max(1, m))); for (const r of rows) { r.survives_bonferroni = r.z != null && Math.abs(r.z) >= z_adj && Math.abs(r.d) >= 0.3; } return { rows, m, z_adj: Math.round(z_adj * 100) / 100, n_signal: rows.filter((r) => r.verdict === "SIGNAL").length, n_signal_bonf: rows.filter((r) => r.survives_bonferroni).length, n_weak: rows.filter((r) => r.verdict === "weak").length, n_null: rows.filter((r) => r.verdict === "null/noise").length, n_insuff: rows.filter((r) => r.verdict === "insufficient-n").length }; } var meta = (st) => ({ domain: st.domain, id: st.id, label: st.label, lore: !!st.lore, expect: st.expect, confoundVar: !!st.confoundVar }); var flat = (e) => ({ verdict: e.verdict, d: e.cohen_d ?? null, z: e.z ?? null, n: `${e.n_lo}/${e.n_hi}` }); // scripts/lib/experimental/advancedChassisPresentation.mjs var ADVANCED_SOURCE = { YOUR_DATA: "[YOUR DATA]", BUILDER: "[BUILDER]", PRINCIPLE: "[PRINCIPLE]", INDUSTRY: "[INDUSTRY]", CLASS: "[CLASS REF]", REFERENCE: "[REFERENCE]", EVOLUTION: "[EVOLUTION]", ABA: "[A-B-A]" }; var REC_TIER = { BOOK: { id: "book", label: "BOOK", hint: "Strong enough to run tonight \u2014 confirm with A\u2032" }, VALIDATE: { id: "validate", label: "VALIDATE", hint: "Direction is clear \u2014 one clean A-B-A before stacking" }, REFERENCE: { id: "reference", label: "REFERENCE", hint: "Background context \u2014 your log overrides this" }, EXPLORE: { id: "explore", label: "EXPLORE", hint: "Low data \u2014 test only if nothing else is booked" } }; var ADVANCED_CHASSIS_CAVEAT = "Reference from public and community sources. Logged scale, bar turns, wing, stagger, and A-B-A data on this chassis take priority."; var ADVANCED_THIN_DATA_CAVEAT = "Limited published geometry for this builder. Your scale sheet and session log are the authority; class and industry priors are reference frame only."; var ADVANCED_DATA_LEADS_NOTE = "Your logged findings and tonight's scale row drive this output. Builder and geometry notes fill gaps only."; var ADVANCED_SCAFFOLD_CAVEAT_SUFFIX = " Reference context only while your log builds depth."; var ADVANCED_CONTEXT_GAP_CAVEAT = "Optional \u2014 add fields in Garage \u2192 Setup so logged data can override reference notes."; var ADVANCED_PERSONALIZED_CAVEAT = "From your logged setup on file. Advanced builders vary car-to-car; confirm with A-B-A before booking a move."; function tagReason(tier, text) { return `${tier} ${text}`; } function formatRecommendationTier(r) { if (!r) return REC_TIER.EXPLORE; if (r.flags?.pivot_downgraded || r.flags?.pivot_reference_only) return REC_TIER.REFERENCE; if (r.basis === "finding") { return r.confidence === "HIGH" ? REC_TIER.BOOK : REC_TIER.VALIDATE; } if (r.flags?.try_first) { if (r.flags?.advanced_try_first && r.confidence === "MODERATE") return REC_TIER.VALIDATE; if (r.flags?.builder_signal && !r.flags?.scaffold) return REC_TIER.VALIDATE; if (r.flags?.track_evolution_conflict) return REC_TIER.BOOK; if (r.flags?.advanced_between_session) return REC_TIER.VALIDATE; if (r.lever === "advanced_log_minimum") return REC_TIER.BOOK; return REC_TIER.VALIDATE; } if (r.flags?.aba_discipline && r.lever?.startsWith("aba_interpret")) return REC_TIER.BOOK; if (r.flags?.aba_discipline) return REC_TIER.VALIDATE; if (r.flags?.builder_signal && !r.flags?.scaffold) return REC_TIER.VALIDATE; if (r.flags?.advanced_track_evolution && r.flags?.advanced_forward_plan) return REC_TIER.VALIDATE; if (r.flags?.late_model_evolution && r.flags?.advanced_forward_plan) return REC_TIER.VALIDATE; if (r.flags?.scaffold || r.flags?.public_baseline) return REC_TIER.REFERENCE; if (r.confidence === "MODERATE") return REC_TIER.VALIDATE; return REC_TIER.EXPLORE; } function buildAdvancedDataQualityNote(assessment, sessionLog = [], opts = {}) { const log = sessionLog || []; const scaleRows = log.filter( (r) => r && (r.rf_psi != null || r.lf_psi != null || r.stagger != null || r.lr_bar_turns != null) ); const stateRows = opts.trackState ? log.filter((r) => r && r.track_state && String(r.track_state).toLowerCase() === String(opts.trackState).toLowerCase()) : scaleRows; const gaps = []; const unlocks = []; if (scaleRows.length < 2) { const need = 2 - scaleRows.length; gaps.push(`${need} more scale row${need > 1 ? "s" : ""} at this track`); unlocks.push("car-specific stagger/bar/wing defaults instead of class bands"); } else if (scaleRows.length < 4) { gaps.push(`${4 - scaleRows.length} more logged run${4 - scaleRows.length > 1 ? "s" : ""} for median-based guidance`); unlocks.push("history-weighted try-first moves"); } if (opts.trackState && stateRows.length < 2 && scaleRows.length >= 1) { gaps.push(`1+ run logged in ${opts.trackState} track state`); unlocks.push("state-specific lever order (tacky vs slick)"); } if (assessment) { if (!assessment.hasChassis) { gaps.push("chassis builder on file"); unlocks.push("Mach 1 / J&J / KRK / GRT builder signals"); } if (!assessment.hasCoreMeas) { gaps.push("scale sheet (psi, stagger, bar turns)"); unlocks.push("personalized cross/stagger context"); } if (!assessment.hasFeel) { gaps.push("entry/mid/exit feel"); unlocks.push("phase-first bar vs wing ordering"); } if (!assessment.hasGeometryState && assessment.hasDeepBuilder) { gaps.push("PIVOT pickup points"); unlocks.push("repeatable RC comparisons between nights"); } } const pivot = opts.pivotStatus || opts.geometryTrust?.pivot; if (pivot?.stale) { gaps.unshift("re-measure PIVOT pickups (platform changed)"); unlocks.push("car-anchored bar/wing order"); } else if (pivot && pivot.expected > 0 && pivot.measured < pivot.expected) { const miss = pivot.missingShocks?.length ? `${pivot.missingShocks.length} shock mount${pivot.missingShocks.length > 1 ? "s" : ""}` : `${pivot.expected - pivot.measured} remaining pickup${pivot.expected - pivot.measured > 1 ? "s" : ""}`; gaps.push(`PIVOT ${pivot.measured}/${pivot.expected} \u2014 ${miss}`); unlocks.push("geometry-specific RC guidance"); } else if (pivot?.complete) { return { headline: "PIVOT on file \u2014 recommendations can anchor to your measured pickups.", gaps: [], unlocks: ["car-specific lever order", "RC comparisons between nights"], strength: "strong" }; } if (!gaps.length) { if (assessment?.completeness >= 70 && scaleRows.length >= 3) { return { headline: "Data depth is solid \u2014 recommendations weight your history first.", gaps: [], unlocks: [], strength: "strong" }; } return { headline: "Decent context on file \u2014 log each session row to keep guidance sharp.", gaps: [], unlocks: [], strength: "moderate" }; } const headline = gaps.length === 1 ? `Add ${gaps[0]} for stronger guidance.` : `To sharpen output: ${gaps.slice(0, 3).join(" \xB7 ")}.`; return { headline, gaps: gaps.slice(0, 4), unlocks: unlocks.slice(0, 3), strength: scaleRows.length >= 1 ? "building" : "thin" }; } function advancedRecTag(r) { if (r.basis === "finding") return ADVANCED_SOURCE.YOUR_DATA; const lev = String(r.lever || ""); if (/^advanced_geo_/.test(lev)) { return r.flags?.scaffold ? "[REF \xB7 GEOMETRY]" : "[PRINCIPLE \xB7 GEOMETRY]"; } if (r.flags?.builder_signal) { const mfr2 = (r.flags.builder_mfr || "BUILDER").toUpperCase(); return r.flags?.scaffold ? `[REF \xB7 ${mfr2}]` : `[BUILDER \xB7 ${mfr2}]`; } if (r.flags?.aba_discipline) { return r.lever?.startsWith("aba_interpret") ? "[A-B-A \xB7 READ]" : "[A-B-A]"; } if (r.flags?.advanced_track_evolution || r.flags?.late_model_evolution) { if (r.flags?.track_evolution_conflict) return "[CONFLICT]"; return r.flags?.advanced_forward_plan ? "[EVOLUTION \xB7 PLAN]" : "[EVOLUTION]"; } if (r.flags?.track_snap || r.flags?.advanced_track_snap) return "[TRACK SNAP]"; if (r.flags?.thin_data_chassis && r.flags?.advanced_chassis) { return r.flags?.scaffold ? "[REF \xB7 BUILDER]" : "[BUILDER REF]"; } if (r.flags?.advanced_logging) return r.flags?.scaffold ? "[REF \xB7 LOG]" : "[LOG \xB7 ADV]"; if (r.lever === "advanced_log_minimum") return "[LOG \xB7 ADV]"; if (r.lever === "advanced_data_priority" || r.lever === "thin_data_priority") { return "[DATA POLICY]"; } const mfr = r.flags?.chassis_mfr; if (mfr && /^mach1_|^jj_|^krk_|^fletcher_/.test(lev)) { return r.flags?.scaffold ? `[REF \xB7 ${String(mfr).toUpperCase()}]` : `[BUILDER \xB7 ${mfr}]`; } if (r.flags?.advanced_chassis) { return r.flags?.scaffold ? "[REF \xB7 ADV]" : "[ADV REF]"; } return null; } function buildAdvancedDataNote(opts = {}) { const { nFind = 0, nScaffold = 0, nGeo = 0, nBuilder = 0, chassisLabel = null, hasLoggedFeel = false, sessionLogCount = 0, scaleRowCount = 0 } = opts; if (nFind > 0) { let note = `${nFind} logged finding${nFind > 1 ? "s" : ""} on this car \u2014 ${ADVANCED_DATA_LEADS_NOTE}`; if (nScaffold) { note += ` (${nScaffold} reference hint${nScaffold > 1 ? "s" : ""} for levers your log has not covered yet.)`; } if (!hasLoggedFeel) { note += " Log entry/mid/exit feel to tighten phase-specific lever choice."; } return note; } if (scaleRowCount >= 3) { const parts2 = []; if (nBuilder) parts2.push(chassisLabel ? `${chassisLabel} builder context` : "builder context"); if (nGeo) parts2.push("geometry principles"); parts2.push("class baseline"); return `${scaleRowCount} scale rows at this track \u2014 guidance weights your history. Reference layer: ${parts2.join(" + ")}.`; } const parts = []; if (nGeo) parts.push("geometry principles"); if (nBuilder) parts.push(chassisLabel ? `${chassisLabel} reference` : "builder reference"); parts.push("class baseline"); const logHint = sessionLogCount > 0 ? `${sessionLogCount} session note${sessionLogCount > 1 ? "s" : ""} on file \u2014 add scale rows to unlock car-specific moves.` : "Log scale + bar/wing + phase feel to move reference notes to the background."; return `No logged findings yet \u2014 ${parts.join(" + ")}. ${logHint}`; } function advancedDriverBriefContextLine(assessment) { if (!assessment || assessment.completeness >= 35) return null; return "Car on file: class reference only \u2014 add chassis builder, scale sheet, and track state to sharpen output."; } var ADVANCED_COACH_HEADER = "ADVANCED CHASSIS \xB7 YOUR LOG LEADS"; // scripts/lib/experimental/advancedChassisKnowledge.mjs var ADVANCED_DATA_PRIORITY = { lever: "advanced_data_priority", action: "Data policy: log scale sheet, LR/RR bar turns, wing angle, stagger, and entry/mid/exit feel every session \u2014 at this level, car-to-car variance exceeds anything public notes can capture", reason: "[DATA POLICY] Your A-B-A log is the authority; public and builder notes are reference until your findings exist" }; var SHARED_SQUARING = { lever: "advanced_shared_squaring", action: "Squaring: rear axle square to frame \xB7 birdcage timing and radius rod lengths verified before bar or shock work \xB7 carrier hole logged each scale", reason: "[INDUSTRY] Bad squaring masks real setup learning on torsion-bar sprint cars" }; var SHARED_SCALING = { lever: "advanced_shared_scaling", action: "Scaling: level pads per builder card \u2192 torsion arms neutral on blocks \u2192 driver + race fuel in \u2192 record bar stop turns and corner weights \xB7 rematch tire circumference before comparing nights", reason: "[INDUSTRY] Stagger and tire size shift effective cross \u2014 bar turns belong on every scale sheet" }; var SHARED_TRACK_STATE = { lever: "advanced_shared_track_state", action: "Track evolution: tacky \u2014 set wing/stagger baseline early \xB7 drying \u2014 stagger or LR bar before shock stack \xB7 slick \u2014 protect RR, wing step separate from bar turn \xB7 log transition every session", reason: "[INDUSTRY] Feature setup is planned from heat notes, not a static cross from lap one" }; var SHARED_DIRECTION = { lever: "advanced_shared_direction", action: "Phase diagnosis: entry push \u2192 RF/LF platform or front bar before rear tighten \xB7 exit loose \u2192 LR bite (bar/stagger) before wing \xB7 tight in/loose off \u2192 entry fix this run, exit next run", reason: "[INDUSTRY] One lever per run \u2014 stacking bar + wing + stagger in one heat hides cause" }; function normalizeTrack(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (["tacky", "rubbered", "drying"].includes(s)) return "tacky"; return "unknown"; } function advancedChassisSharedCore(trackState) { const track = normalizeTrack(trackState); const priors = [ ADVANCED_DATA_PRIORITY, SHARED_SQUARING, SHARED_SCALING, SHARED_TRACK_STATE, SHARED_DIRECTION ]; if (track === "slick" || track === "drying") { priors.push({ lever: "advanced_shared_slick", action: "Slick/drying: take stagger out before LR bar rate up \xB7 wing down step before second bar turn \xB7 log hot RR psi after laps 8\u201310", reason: "[INDUSTRY] Freed tracks punish stacked bite \u2014 isolate one lever per run" }); } if (track === "wet") { priors.push({ lever: "advanced_shared_wet", action: "Wet/heavy: conservative wing + stagger pairing opening laps \xB7 RS psi band before hero bar changes \xB7 plan feature loosen moves early", reason: "[INDUSTRY] Moisture stacks grip \u2014 baseline scale before chasing" }); } return priors; } function withAdvancedCaveat(priors, brandCaveat) { const caveat = brandCaveat || ADVANCED_CHASSIS_CAVEAT; return priors.map((p) => ({ ...p, caveat })); } // scripts/lib/experimental/advancedDriverBuilderPhilosophy.mjs var DRIVER_BUILDER_MINDSET = { lever: "advanced_db_mindset", action: "Driver/builder mindset (advanced): balance over extremes \u2014 the car should talk through the seat \xB7 small targeted moves beat stacking three knobs \xB7 if the driver cannot repeat the lap, fix repeatability before chasing speed", reason: "builder-driver model (advanced sprint): successful shops optimize for predictable feel, not hero numbers" }; var DRIVER_BUILDER_SEAT_FEEDBACK = { lever: "advanced_db_seat_feedback", action: 'Seat feedback first (advanced): log WHERE it pushes or frees (entry/mid/exit) before touching psi, bar, or wing \xB7 one change \u2192 same line \u2192 same feel = valid A-B-A \xB7 vague "loose" without phase is not enough data', reason: "builder-driver model: builders who drive their own cars tune from direct feedback \u2014 your log should mirror that" }; var DRIVER_BUILDER_REPEATABILITY = { lever: "advanced_db_repeatability", action: "Repeatability (advanced): reset to builder baseline card when the night drifts \xB7 record scale + bar turns every session \xB7 compare nights only when tire circumference and fuel load match", reason: "builder-driver model: advanced teams treat the scale sheet as the memory \u2014 not the driver's recall" }; var DRIVER_BUILDER_SYSTEMS = { lever: "advanced_db_systems", action: "Systems before knobs (advanced): squaring and birdcage timing are the foundation \xB7 shocks and bars fine-tune a square car \u2014 chasing bite on bad geometry teaches the wrong lesson", reason: "builder-driver model: fabrication-first shops fix the platform, then make one suspension move" }; function driverBuilderPhilosophyCore(opts = {}) { const max = opts.maxPriors ?? 4; return [ DRIVER_BUILDER_MINDSET, DRIVER_BUILDER_SEAT_FEEDBACK, DRIVER_BUILDER_REPEATABILITY, DRIVER_BUILDER_SYSTEMS ].slice(0, max); } var MACH1_BUILDER_DRIVER = { lever: "mach1_builder_driver", action: "Mach 1 builder-driver (community): Mark Smith builds and races Mach 1 \u2014 shop is near Williams Grove \xB7 builder is often in the pits weekly (Grove, Port Royal, Lincoln) \xB7 track-specific line and setup advice is part of the product", reason: "community (Mach 1 / HoseHeads / USAC): locally built chassis + builder at the track \u2014 not a catalog sheet from out of state" }; var MACH1_CONSTRUCTION = { lever: "mach1_construction", action: "Mach 1 construction (community): in-house fabrication lineage (JEI / Keen outlaw / Elite micro background) \xB7 focus on durable bullring cars built by welders who race \xB7 quality = repeatable geometry and assembly, not exotic one-off tricks", reason: "community (Mach 1 / HoseHeads): builder fab experience \u2014 public discussion emphasizes craft and track support over published tubing specs" }; var MACH1_HANDLING_CHARACTER = { lever: "mach1_handling_character", action: "Mach 1 handling character (community): balanced platform \u2014 entry stability without binding exit \xB7 drivers report builder helps with phase-specific line choice (where to enter, where to drive off) \xB7 tune for confidence through the middle, not max cross on lap 1", reason: 'community (USAC / PA sprint): Mark Smith as mentor \u2014 track-specific balance and drive-off advice over generic "more bite"' }; var MACH1_MATERIALS = { lever: "mach1_materials", action: "Mach 1 materials (community): high-grade 4130 chromoly tubing ladder construction \u2014 emphasis on durability, safety, and repeatable jig/weld quality \xB7 value is birdcage/index consistency car-to-car, not exotic one-off tricks", reason: "community (Mach 1): public info emphasizes build quality, materials, and track support \u2014 not proprietary geometry charts" }; function mach1DriverBuilderPriors(opts = {}) { const max = opts.maxPriors ?? 5; return [ MACH1_BUILDER_DRIVER, MACH1_CONSTRUCTION, MACH1_HANDLING_CHARACTER, MACH1_MATERIALS, DRIVER_BUILDER_MINDSET ].slice(0, max); } function pickMach1DriverBuilderBrief(trackState) { const track = String(trackState || "").toLowerCase(); const base = "Mach 1: balance, seat feedback, one lever per run \u2014 your log leads"; if (/slick|drying|heavy/.test(track)) { return `${base} \xB7 slick: protect repeatability before bite stacks`; } return `${base} \xB7 start from builder baseline card`; } // scripts/lib/experimental/mach1ChassisKnowledge.mjs var MACH1_CAVEAT = `${ADVANCED_CHASSIS_CAVEAT} Mach 1 notes are public builder-driver reference \u2014 confirm with Mach 1 shop or your setup card.`; var PHILOSOPHY = { lever: "mach1_philosophy", action: "Mach 1: PA bullring builder-driver \u2014 Mark Smith races what he builds \xB7 baseline card + one logged change per run \xB7 shop support at Grove / Port Royal / Lincoln is part of the program", reason: "[BUILDER \xB7 Mach 1] Balance and repeatability over stacked hero moves \u2014 community / HoseHeads" }; var POSITIONING = { lever: "mach1_positioning", action: "Mach 1 positioning (community): 410/358 central PA focus \xB7 USAC midget program reported in past seasons \u2014 geometry/squaring before copying Maxim or J&J sheet numbers verbatim", reason: "community (Mach 1 / The Third Turn): builder lineage JEI / Keen / Elite micro \u2014 not a Maxim clone" }; var SCALING = { lever: "mach1_scaling", action: "Mach 1 scaling (industry sprint norm + community): scale with driver + fuel on level pads \xB7 record LR/RR bar stop turns with cross/left/rear/nose \xB7 Mach 1 teams often reset from builder card when scale drifts", reason: "manufacturer/industry sprint norm: torsion stop turns are the repeatable reference \u2014 community Mach 1 teams log turns every night" }; var GEOMETRY = { lever: "mach1_geometry", action: "Mach 1 geometry (community): verify birdcage timing and trailing arm lengths before bar rate chase \xB7 squaring sequence same as sprint industry norm \u2014 ask shop for current carrier hole baseline", reason: "community (Mach 1): builder-specific birdcage holes differ from J&J/Maxim \u2014 do not copy another brand's holes" }; var HANDLING = { lever: "mach1_handling", action: "Mach 1 handling tendencies (community): entry push \u2192 front platform (RH/block height) before LR bar stiffen \xB7 exit loose \u2192 LR bite via bar or stagger before wing forward \xB7 hop \u2192 soften RR comp before bar add", reason: "community sprint dirt (PA bullring generalization applied to Mach 1 customers): phase-first tuning" }; var TRACK_SLICK = { lever: "mach1_track_slick", action: "Mach 1 slick (community): stagger down 1/4 in step before LR bar up \xB7 wing down 0.5\u20131\xB0 separate run \xB7 protect RR for feature drive \u2014 log which move helped on Mach 1 card", reason: "community sprint dirt: Mach 1 teams on PA bullrings plan slick transition in heats" }; var TRACK_TACKY = { lever: "mach1_track_tacky", action: "Mach 1 tacky (community): establish builder baseline scale first session \xB7 heat notes drive feature wing/stagger \u2014 do not max wing + max LR bar same run", reason: "community (Mach 1): tacky nights still need logged scale reference before hero moves" }; var MIDGET_PHILOSOPHY = { lever: "mach1_midget_philosophy", action: "Mach 1 midget (community): USAC/POWRi dirt midget \u2014 panhard/J-bar and LR platform before wing or stagger hero moves \xB7 NOT 410 stagger bands or micro 600 priors", reason: "community (Mach 1 midget): full midget weight and aero differ from winged 410 \u2014 builder midget card is primary" }; var MIDGET_SCALING = { lever: "mach1_midget_scaling", action: "Mach 1 midget scaling (community): scale driver + fuel on blocks per builder card \xB7 log panhard height + LR bar turns with left/rear % \xB7 midget left-side % often higher than big sprint", reason: "community (USAC midget): midget scale drift shows in LR bite before wing \u2014 Mach 1 teams reset from card when cross walks" }; var MIDGET_GEOMETRY = { lever: "mach1_midget_geometry", action: "Mach 1 midget geometry (community): square rear axle to motor plate \xB7 verify RF trailing arm length before copying sprint 410 holes \xB7 midget birdcage index per Mach 1 midget sheet", reason: "community (Mach 1 midget): midget RF arm and panhard holes are not interchangeable with winged 410 Mach 1" }; var MIDGET_HANDLING = { lever: "mach1_midget_handling", action: "Mach 1 midget handling (community): entry push \u2192 RF platform or panhard before LR bar \xB7 exit loose \u2192 LR bite (bar/stagger) before wing angle \xB7 tight in/loose off \u2192 entry fix this run on 1/4 mi bullrings", reason: "community (Mach 1 / USAC midget dirt): shorter wheelbase magnifies phase errors \u2014 one lever per run" }; var MIDGET_TRACK_SLICK = { lever: "mach1_midget_track_slick", action: "Mach 1 midget slick (community): take rear stagger out 1/8\u20131/4 in before LR bar up \xB7 wing down step separate run \xB7 protect RR for feature on USAC-style tracks", reason: "community (Mach 1 midget): slick transition planned from heat scale \u2014 not static midget cross all night" }; var MIDGET_TRACK_TACKY = { lever: "mach1_midget_track_tacky", action: "Mach 1 midget tacky (community): establish midget baseline scale first session \xB7 heat notes drive feature panhard/stagger \u2014 do not max wing + max LR bar same run", reason: "community (Mach 1 midget): tacky midget nights still need logged scale before hero moves" }; function isMach1MidgetProfile(enrichedVc = {}, profile2 = "") { if (profile2 === "hyper_midget") return true; const cls = String(enrichedVc.car_class || enrichedVc.class_name || "").toLowerCase(); return /midget|usac|powri/.test(cls) && !/micro|quarter|mini midget 600/.test(cls); } function mach1ChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 12; const profile2 = opts.profile || ""; const track = String(trackState || "").toLowerCase(); const isSlick = ["dry_slick", "slick", "heavy", "drying"].some((k) => track.includes(k)); const isMidget = isMach1MidgetProfile(enrichedVc, profile2); if (isMidget) { const midgetPriors = [ ...mach1DriverBuilderPriors({ maxPriors: 3 }), MIDGET_PHILOSOPHY, ...advancedChassisSharedCore(trackState).slice(0, 2), MIDGET_SCALING, MIDGET_GEOMETRY, MIDGET_HANDLING, isSlick ? MIDGET_TRACK_SLICK : MIDGET_TRACK_TACKY, { lever: "mach1_midget_wing_note", action: "Mach 1 midget wing (community): winged midget \u2014 wing angle is entry knob but panhard/LR platform sets baseline \xB7 non-wing midget: stagger and LR bite before front geometry chase", reason: "community (Mach 1 midget): wing rules vary by series \u2014 log wing angle with bar turns every session" } ]; return withAdvancedCaveat(midgetPriors, MACH1_CAVEAT).slice(0, max); } const priors = [ ...mach1DriverBuilderPriors({ maxPriors: 4 }), PHILOSOPHY, POSITIONING, ...advancedChassisSharedCore(trackState).slice(0, 2), SCALING, GEOMETRY, HANDLING, isSlick ? TRACK_SLICK : TRACK_TACKY ]; return withAdvancedCaveat(priors, MACH1_CAVEAT).slice(0, max); } function pickMach1PhilosophyLine(trackState) { return pickMach1DriverBuilderBrief(trackState); } // scripts/lib/experimental/jjSprintChassisKnowledge.mjs var JJ_CAVEAT = `${ADVANCED_CHASSIS_CAVEAT} J&J notes here are community-reported patterns \u2014 use your J&J setup card as primary.`; var PHILOSOPHY2 = { lever: "jj_philosophy", action: "J&J: start from dealer setup card for your model year \xB7 log bar turns + scale every session \xB7 one change per run \u2014 holes and bar bands differ from Maxim/Mach 1/KRK", reason: "[BUILDER \xB7 J&J] Dominant sprint builder \u2014 cross-brand sheet copy is unreliable" }; var SCALING2 = { lever: "jj_scaling", action: "J&J scaling (community): J&J customers often scale driver-in with race fuel \xB7 record LR/RR stop turns with left/rear/nose/cross \xB7 remeasure when tire circumference or stagger changes", reason: "community (J&J): repeatable scale sheet is the product \u2014 public forums rarely publish exact J&J percentages" }; var GEOMETRY2 = { lever: "jj_geometry", action: "J&J geometry (community): birdcage timing and trailing arm length per J&J card before copying Maxim RF arm or stagger numbers \xB7 squaring per builder sequence", reason: "community (J&J vs Maxim): RF arm length and RR offset bands differ by builder \u2014 chassis-specific card wins" }; var HANDLING2 = { lever: "jj_handling", action: "J&J handling tendencies (community): entry push \u2192 front RH/block or RF platform before LR bar \xB7 exit loose \u2192 LR stagger/bar before wing \xB7 tight in/loose off \u2192 entry platform this run only", reason: "community sprint dirt (J&J teams): same phase rule as other advanced builders \u2014 log feel by corner" }; var TRACK_STATE = { lever: "jj_track_state", action: "J&J track state (community): tacky baseline from card \xB7 drying \u2192 stagger down before bar stiffen \xB7 slick \u2192 wing step + RR protection before second LR turn", reason: "community (J&J sprint): feature setup planned from heat scale drift \u2014 not static cross all night" }; function jjSprintChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 10; const priors = [ PHILOSOPHY2, ...driverBuilderPhilosophyCore({ maxPriors: 2 }), ...advancedChassisSharedCore(trackState).slice(0, 3), SCALING2, GEOMETRY2, HANDLING2, TRACK_STATE ]; return withAdvancedCaveat(priors, JJ_CAVEAT).slice(0, max); } function pickJjPhilosophyLine() { return PHILOSOPHY2.action; } // scripts/lib/experimental/krkChassisKnowledge.mjs var KRK_CAVEAT = `${ADVANCED_CHASSIS_CAVEAT} KRK/Keen notes are community-reported \u2014 confirm with KRK setup guidance for your model.`; var PHILOSOPHY3 = { lever: "krk_philosophy", action: "KRK / Keen Speed: start from KRK baseline card \xB7 log bar turns + left/rear % every scale \xB7 shop support common on PA bullrings \u2014 do not cross-copy J&J or Mach 1 holes", reason: "[BUILDER \xB7 KRK] Sprint program is separate from Keen outlaw micro \u2014 cross-brand copy fails" }; var SCALING3 = { lever: "krk_scaling", action: "KRK scaling (community): scale driver-in \xB7 log LR/RR bar preload turns with corner weights \xB7 KRK teams often chase left/rear % before wing on dirt", reason: "community (KRK sprint): left-side and rear % reported as primary balance levers on dirt" }; var GEOMETRY3 = { lever: "krk_geometry", action: "KRK geometry (community): verify axle square and birdcage index before offset or bar chase \xB7 trailing arm holes per KRK card \u2014 not J&J or Maxim hole chart", reason: "community (KRK): builder-specific geometry index \u2014 public hole numbers rarely published" }; var HANDLING3 = { lever: "krk_handling", action: "KRK handling tendencies (community): loose exit \u2192 LR bar or stagger before wing forward \xB7 push entry \u2192 front block/RH before LR stiffen \xB7 hop \u2192 RR platform before bar stack", reason: "community sprint dirt (KRK customers): forward bite and LR side bite commonly discussed on forums" }; var TRACK_STATE2 = { lever: "krk_track_state", action: "KRK track state (community): wet \u2192 conservative wing + stagger pairing \xB7 drying \u2192 take rear stagger out before LR bar up \xB7 slick \u2192 RR psi/stagger before second wing step", reason: "community (KRK sprint): track evolution drives stagger/bar plan \u2014 log every heat" }; function krkChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 10; const priors = [ PHILOSOPHY3, ...driverBuilderPhilosophyCore({ maxPriors: 2 }), ...advancedChassisSharedCore(trackState).slice(0, 3), SCALING3, GEOMETRY3, HANDLING3, TRACK_STATE2 ]; return withAdvancedCaveat(priors, KRK_CAVEAT).slice(0, max); } function pickKrkPhilosophyLine() { return PHILOSOPHY3.action; } // scripts/lib/experimental/fletcherSprintChassisKnowledge.mjs var FLETCHER_CAVEAT = `${ADVANCED_CHASSIS_CAVEAT} Fletcher notes here are community-reported \u2014 use your Fletcher setup card or shop guidance as primary.`; var PHILOSOPHY4 = { lever: "fletcher_philosophy", action: "Fletcher: dealer baseline card for your model year \xB7 log bar turns + scale every session \xB7 one change per run \u2014 holes and bar bands differ from J&J/Maxim/Mach 1", reason: "[BUILDER \xB7 Fletcher] Builder-specific geometry index \u2014 cross-brand hole charts are unreliable" }; var POSITIONING2 = { lever: "fletcher_positioning", action: "Fletcher positioning (community): bullring sprint focus \u2014 verify birdcage timing and trailing arm lengths before RF arm or stagger copy from another brand", reason: "community (Fletcher sprint): geometry index is builder-specific \u2014 public forums rarely publish exact Fletcher carrier holes" }; var SCALING4 = { lever: "fletcher_scaling", action: "Fletcher scaling (community): scale driver-in with race fuel on level pads \xB7 record LR/RR bar stop turns with left/rear/nose/cross \xB7 remeasure tire circumference before comparing nights", reason: "community (Fletcher): repeatable scale sheet beats hero wing/bar moves \u2014 log turns every heat" }; var GEOMETRY4 = { lever: "fletcher_geometry", action: "Fletcher geometry (community): square rear axle to frame per builder sequence \xB7 birdcage timing before bar rate chase \xB7 ask shop for current RF/RR arm baseline holes", reason: "manufacturer/industry sprint norm + community (Fletcher): squaring first \u2014 Fletcher hole charts differ from J&J and KRK" }; var HANDLING4 = { lever: "fletcher_handling", action: "Fletcher handling tendencies (community): entry push \u2192 front RH/block or RF platform before LR bar stiffen \xB7 exit loose \u2192 LR stagger/bar before wing \xB7 hop \u2192 RR platform before bar stack", reason: "community sprint dirt (Fletcher customers): phase-first tuning \u2014 log feel by corner" }; var TRACK_STATE3 = { lever: "fletcher_track_state", action: "Fletcher track state (community): tacky \u2192 establish builder baseline scale first \xB7 drying \u2192 stagger down before LR bar up \xB7 slick \u2192 wing step separate from second bar turn \xB7 log transition every heat", reason: "community (Fletcher sprint): feature setup from heat notes \u2014 not one static cross all night" }; function fletcherSprintChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 10; const profile2 = opts.profile || ""; const isMidget = profile2 === "hyper_midget" || /midget|usac|powri/.test(String(enrichedVc.car_class || enrichedVc.class_name || "").toLowerCase()); const priors = [ PHILOSOPHY4, POSITIONING2, ...driverBuilderPhilosophyCore({ maxPriors: 2 }), ...advancedChassisSharedCore(trackState).slice(0, 3), SCALING4, GEOMETRY4, HANDLING4, TRACK_STATE3 ]; if (isMidget) { priors.splice(2, 0, { lever: "fletcher_midget_note", action: "Fletcher midget (community): full midget scale and panhard/J-bar primary on dirt \xB7 NOT winged 410 stagger bands \xB7 use Fletcher midget baseline card", reason: "community (Fletcher midget): midget geometry and left-side % differ from big sprint \u2014 builder midget sheet wins" }); } return withAdvancedCaveat(priors, FLETCHER_CAVEAT).slice(0, max); } function pickFletcherPhilosophyLine(trackState) { const track = String(trackState || "").toLowerCase(); if (/slick|drying|heavy/.test(track)) { return `${PHILOSOPHY4.action} \xB7 drying/slick: stagger down before LR bar up`; } return PHILOSOPHY4.action; } // scripts/lib/experimental/outlawKartGrassrootsKnowledge.mjs var OUTLAW_KART_DEEP_CAVEAT = "Public baseline from manufacturer documentation, build videos, and community knowledge \u2014 validate with your own data and specific chassis setup."; var DEEP_BUILDERS = /* @__PURE__ */ new Set(["slack", "toigo", "phantom", "qrc", "ultramax", "chaos"]); function normalizeTrack2(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; return "normal"; } function withCaveat(priors) { return priors.map((p) => ({ ...p, caveat: OUTLAW_KART_DEEP_CAVEAT })); } var WINGED_GENERAL_PHILOSOPHY = { lever: "outlaw_winged_philosophy", action: "Winged outlaw order: scales baseline (nose/left/cross family) \u2192 seat + rear axle slot \u2192 align toe/caster \u2192 inch stagger for middle \u2192 wing 0.5\xB0 steps \u2192 psi last \u2014 floating cage karts live or die on seat and axle before fine cross", reason: "Community (No Goats winged outlaw PDF): success starts on the scales \u2014 seat and axle move nose/rear; washers and stagger move cross" }; var COMMUNITY_LOW_CROSS_SCALING = { lever: "outlaw_low_cross_scaling", action: "Low-cross winged outlaw (QRC/SKE/suspended community): cross ~52\u201356% \xB7 left ~54\u201358% \xB7 nose ~40\u201346% with wing \xB7 RF narrow as designed \u2014 if RR hops when track grips, cross is above this window", reason: "Community (No Goats low-cross kart sheet): cross 53\u201354% / left 55\u201356% / nose 44\u201345% start \u2014 not Pursuit 64%+ family" }; var COMMUNITY_HIGH_CROSS_SCALING = { lever: "outlaw_high_cross_scaling", action: "High-cross winged outlaw (Pursuit/Toigo/Phantom community): cross ~59\u201366% \xB7 left ~54\u201358% \xB7 nose ~44\u201346% \xB7 add cross to four-wheel drift then back ~2% is a common dirt approach", reason: "Community (No Goats high-cross kart sheet): cross 59\u201365% / left 54\u201355% \u2014 different chassis family than QRC low-cross" }; var COMMUNITY_NOSE_SEAT = { lever: "outlaw_nose_seat", action: "Nose via seat (winged outlaw): pushing/tight entry \u2192 seat forward (~0.25\u20130.5% nose steps) \xB7 loose entry \u2192 seat back \xB7 fix nose/rear on scales before 1/8 in stagger band-aids \xB7 ballast low on seat/back", reason: "Community (No Goats PDF): seat fore/aft is primary nose lever on cage karts \u2014 front/rear % only moves with seat or ballast" }; var COMMUNITY_LEFT_SIDE = { lever: "outlaw_left_side", action: "Left side % (winged outlaw): target ~54\u201358% on dirt winged \xB7 wet/soft tracks often want lower left + lower cross \xB7 fast/grippy tracks want more left and/or more cross \xB7 flat track \u2192 more left; banked \u2192 less left vs flat", reason: "Community (No Goats PDF): left side controls side bite on RS tires \u2014 VCG/seat height shifts the left % you need" }; var COMMUNITY_CROSS_MECHANICS = { lever: "outlaw_cross_weight", action: "Cross mechanics (winged outlaw): ~1 rear height turn \u2248 1% cross \xB7 RF spindle up = cross up \xB7 front stagger changes cross \xB7 rear axle down (chassis up) raises cross \xB7 log low-cross vs high-cross family before copying a number", reason: "Community (No Goats PDF): cross is fine-tune AFTER nose/left are close \u2014 wrong family causes RR hop (low-cross) or lazy push (mis-timed high-cross)" }; var COMMUNITY_GEOMETRY = { lever: "outlaw_geometry", action: "Front end (winged outlaw community): RF toe 0 \xB7 LF ~1/16 in toe out \xB7 caster split ~2\xB0 \xB7 RF caster ~8\u201313\xB0 by grip (10\u201312\xB0 dirt start) \xB7 RF camber ~-2.5 to -3\xB0 \xB7 LF ~+0.5 to +1\xB0 \xB7 re-align after spindle/stagger washers", reason: "Community (No Goats + Slack Pursuit): offset cage binds if toe/camber drift \u2014 narrow RF needs more caster than flat-kart clones" }; var COMMUNITY_SQUARING = { lever: "outlaw_squaring", action: "Alignment (winged outlaw): level scales \xB7 RF toe first then square LF \xB7 rear axle level side-to-side \xB7 diagonal kingpin check catches twisted cage \xB7 bent rear bumper binds floater \u2014 torque consistently (~200 in-lb Pursuit ref)", reason: "Community (No Goats + Slack): geometry first \u2014 large spindle moves need full re-align and re-scale" }; var COMMUNITY_RIDE_HEIGHT = { lever: "outlaw_ride_height", action: "Ride height / CG (winged outlaw): keep driver mass low \xB7 raise rear axle slot (lower chassis) frees kart \xB7 lower rear axle adds grip \xB7 rear axle down \u2248 raises cross ~1% per turn \u2014 set axle hole before cross chasing", reason: "Community (No Goats PDF): CGH changes transfer more than one cross click on cage karts" }; var COMMUNITY_AXLE = { lever: "outlaw_axle_position", action: "Rear axle slot (floating cage): forward axle helps rotation on short/banked 1/8 mi \xB7 rearward adds nose % and can tighten entry \xB7 typical 4+ holes each direction \u2014 pick baseline slot, log mm/hole every night", reason: "Community (No Goats + QRC forum): axle fore/aft is a major lever \u2014 moves nose, cross, and rotation together" }; var COMMUNITY_STAGGER_BASE = { lever: "outlaw_stagger_base", action: "Stagger start (No Goats winged ref): front ~1.25\u20132 in (start ~1.5 in) \xB7 rear ~1/4\u20131 in (start ~3/4 in) \xB7 rear stagger = middle rotation \xB7 front stagger mostly jacks cross \xB7 1/8 in rear step per run", reason: "Community (No Goats PDF): stagger for middle \u2014 LF+RR entry, RF+LR exit; too much rear stagger scrubs straight speed" }; function staggerPrior(track) { if (track === "slick" || track === "drying") { return { lever: "outlaw_stagger", action: "Stagger slick/freeing (winged outlaw): front ~1.25\u20131.5 in \xB7 rear ~3/4\u20131 in (QRC banked ref ~7/8 in) \u2014 take rear out before wing add; less stagger tightens as track frees", reason: "Community (No Goats + QRC forum): slick transition = less rear stagger before cross \u2014 one 1/8 in step per run" }; } if (track === "wet" || track === "greasy") { return { lever: "outlaw_stagger", action: "Stagger heavy/greasy (winged outlaw): front ~1.5\u20131.75 in \xB7 rear ~1\u20131.5 in upper band if loose \u2014 lower left + lower cross often pair on soft tracks \xB7 avoid max stagger + max cross same heat", reason: "Community (No Goats PDF): wet/soft likes lower cross and left \u2014 stagger is middle-phase after nose/cross family set" }; } return { ...COMMUNITY_STAGGER_BASE, action: "Stagger tacky/normal (winged outlaw): front ~1.5 in \xB7 rear ~3/4\u20131.5 in on 1/8 mi \xB7 LF+RR entry \xB7 RF+LR exit \xB7 stagger primarily middle \u2014 log hot circumference after psi stable" }; } function crossPrior(track, family) { const isLow = family === "low"; if (track === "slick" || track === "drying") { return { lever: "outlaw_cross_track", action: isLow ? "Cross slick (low-cross winged): target ~50\u201354% \u2014 take cross out 0.5% before rear stagger chase \xB7 dry slick + high cross often binds exit on suspended tread" : "Cross slick (high-cross winged): may run ~57\u201362% vs tacky \u2014 still drop 0.5\u20131% if exit binds \xB7 four-wheel drift tracks want less cross when tires are on", reason: "Community (No Goats + forum): slick often wants less cross \u2014 diagnose exit bind vs entry push before adding cross back" }; } if (track === "wet" || track === "greasy") { return { lever: "outlaw_cross_track", action: isLow ? "Cross heavy (low-cross winged): ~52\u201356% band \u2014 soft tracks stack grip; RR hop means cross still too high \xB7 pair with lower left if RS overheats" : "Cross heavy (high-cross winged): start mid-band ~61\u201363% \u2014 heavy tracks stack wing + cross quickly; conservative first laps", reason: "Community (No Goats PDF): cool/wet/soft likes lower left and often lower cross vs fast tacky" }; } return isLow ? COMMUNITY_LOW_CROSS_SCALING : COMMUNITY_HIGH_CROSS_SCALING; } var COMMUNITY_WING = { lever: "outlaw_wing", action: "Wing (winged outlaw): 0.5\xB0 steps \xB7 wing rail fore/aft moves angle and load together \xB7 high bank 1/8 mi stacks wing + cross quickly \u2014 log angle + rail with cross each session", reason: "Community (winged outlaw): cage wing is entry-balance lever \u2014 wing back tightens entry on many floater setups" }; var COMMUNITY_TIRE = { lever: "outlaw_tire_psi", action: "Tire psi (tread outlaw): log cold/hot \xB7 RS band ~5\u201312 psi by grip \xB7 suspended tread often \u22646.5 hot \xB7 higher RS loosens \xB7 lower RS tightens exit \xB7 left split often 4\u201310 / right 5\u201312 \xB7 1/4 psi steps after compound correct", reason: "Community (No Goats PDF): psi moves effective cross and stagger \u2014 tires before hero chassis moves" }; var COMMUNITY_TIGHTEN = { lever: "outlaw_direction_tight", action: "Tighten (winged outlaw): less rear stagger \xB7 cross down 0.3\u20130.5% \xB7 wing back \xB7 seat forward if entry push \xB7 RR in \xB7 RF camber more negative if center push \xB7 push = too much rear bite", reason: "Community (No Goats PDF): cross + stagger + wing before second seat move \u2014 one lever per run" }; var COMMUNITY_LOOSEN = { lever: "outlaw_direction_loose", action: "Loosen (winged outlaw): more rear stagger \xB7 cross up 0.5% if LR unloaded \xB7 wing forward \xB7 seat back if loose entry \xB7 RR out within builder limit \xB7 loose = too much front bite", reason: "Community (No Goats PDF): floating cage loose entry often nose too low or cross too high for grip state \u2014 log hot stagger" }; var COMMUNITY_PHASE = { lever: "outlaw_phase_tuning", action: "Phase tuning (winged outlaw): LF+RR \u2192 entry \xB7 RF+LR \u2192 exit \xB7 stagger \u2192 middle \xB7 seat forward fixes push \xB7 seat back fixes loose entry \u2014 diagnose phase before stacking levers", reason: "Community (No Goats PDF): entry vs exit vs middle use different corners on offset cage karts" }; var COMMUNITY_TRACK_CONDITIONS = { lever: "outlaw_track_conditions", action: "Track state (winged outlaw): wet/soft \u2192 lower left + lower cross \xB7 fast/grippy \u2192 more left and/or cross \xB7 flat \u2192 more left % \xB7 banked \u2192 more rear %, less left vs flat \xB7 log tacky \u2192 drying \u2192 slick every session", reason: "Community (No Goats PDF): percentages shift with grip \u2014 baseline sheet is a starting band not a constant" }; var OUTLAW_DRYING_TRANSITION = { lever: "outlaw_drying", action: "Drying transition (winged outlaw): cross down 0.3\u20130.5% before rear stagger add \xB7 rear stagger down 1/8 in per run \xB7 wing back 0.5\xB0 \xB7 re-scale on race tires \u2014 QRC/low-cross RR hop = cross still high for freed grip", reason: "Community (No Goats + QRC forum): tacky \u2192 drying = less cross/stagger before wing on floater cage karts" }; function leftSideForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "outlaw_left_track", action: "Left % slick/drying (winged outlaw): often mid-low ~54\u201356% \xB7 pair with lower cross family \xB7 flat track may keep more left; banked 1/8 often drops left vs tacky", reason: "Community (No Goats PDF): freed grip wants less left + less cross together on RS tires" }; } if (track === "wet") { return { lever: "outlaw_left_track", action: "Left % wet/heavy (winged outlaw): lower left + lower cross common pair \xB7 start ~54% and drop if RS overheats \xB7 avoid max left + max cross on first laps", reason: "Community (No Goats PDF): soft tracks stack mechanical grip \u2014 conservative left/cross pairing" }; } return COMMUNITY_LEFT_SIDE; } function psiForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "outlaw_psi_track", action: "Psi slick/drying (tread outlaw): RS hot often \u22646\u20136.5 suspended tread \xB7 drop RS 1/4 psi before cross add when exit binds \xB7 log cold/hot all four after circumference matched", reason: "Community (No Goats + QRC tread): psi shifts effective cross \u2014 tires before hero chassis on race night" }; } if (track === "wet") { return { lever: "outlaw_psi_track", action: "Psi wet/heavy (tread outlaw): RS upper band 7\u201310 if loose exit after conservative cross \xB7 LS 4\u20138 / RS 5\u201312 split \xB7 1/4 psi steps only after compound correct", reason: "Community (No Goats PDF): moisture allows higher RS band before seat/cross stack" }; } return COMMUNITY_TIRE; } function wingForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "outlaw_wing_track", action: "Wing slick/drying (winged outlaw): 0.5\xB0 steps down from tacky \xB7 wing rail back tightens entry on floaters \xB7 high bank + drying stacks wing+cross \u2014 log rail position + angle each run", reason: "Community (winged outlaw): freeing track = less wing before cross add on cage karts" }; } if (track === "wet") { return { lever: "outlaw_wing_track", action: "Wing wet/heavy (winged outlaw): start lower angle \xB7 add 0.5\xB0 only after cross/stagger conservative \xB7 wing forward helps loose exit if nose % correct on scales", reason: "Community (No Goats PDF): wet stacks grip \u2014 wing + cross together on 1/8 high-bank" }; } return COMMUNITY_WING; } function rideHeightAxleForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "outlaw_axle_track", action: "Axle slot slick/drying (floater): forward axle helps rotation when grip falls \xB7 axle down \u2248 +1% cross per turn \u2014 set slot/hole before cross chase \xB7 log cassette hole every scale", reason: "Community (No Goats + QRC forum): axle fore/aft moves nose, cross, and rotation together on freed tracks" }; } if (track === "wet") { return { lever: "outlaw_axle_track", action: "Axle slot wet/heavy (floater): rearward slot / lower chassis adds grip first laps \xB7 forward axle if push after nose set \xB7 rear axle down raises cross ~1%/turn", reason: "Community (No Goats PDF): moisture \u2014 axle slot before washer cross on cage karts" }; } return COMMUNITY_AXLE; } function rideHeightForTrack(track) { if (track === "wet") { return { lever: "outlaw_ride_height_track", action: "Ride height / CG wet (winged outlaw): keep driver mass low \xB7 lower rear axle slot adds grip \xB7 rear axle down raises cross \u2014 set height/slot before washer cross", reason: "Community (No Goats PDF): CGH + axle slot change transfer more than one cross click when track is soft" }; } if (track === "slick" || track === "drying") { return { lever: "outlaw_ride_height_track", action: "Ride height / CG slick (winged outlaw): raise rear axle slot (lower chassis) frees kart \xB7 set axle hole before cross cut on drying \u2014 log slot with cross each run", reason: "Community (No Goats PDF): freed grip \u2014 CGH and axle slot before second cross change" }; } return COMMUNITY_RIDE_HEIGHT; } function directionPriorsForTrack(track) { if (track === "slick") return [COMMUNITY_TIGHTEN, COMMUNITY_PHASE]; if (track === "drying") return [OUTLAW_DRYING_TRANSITION, COMMUNITY_TIGHTEN]; if (track === "wet") return [COMMUNITY_LOOSEN, COMMUNITY_TIGHTEN]; return [COMMUNITY_LOOSEN, COMMUNITY_TIGHTEN, COMMUNITY_PHASE]; } function wingedOutlawTrackCore(track, family = "high") { return [ COMMUNITY_TRACK_CONDITIONS, crossPrior(track, family), leftSideForTrack(track), staggerPrior(track), psiForTrack(track), wingForTrack(track), rideHeightForTrack(track), rideHeightAxleForTrack(track), ...directionPriorsForTrack(track) ]; } function qrcScalingForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "qrc_scaling_track", action: "QRC scale slick/drying (community + setups.pdf): cross ~50\u201354% \xB7 left ~54\u201356% \xB7 nose ~40\u201344% \xB7 RR hop = cross still too high \u2014 never Toigo/Pursuit 61%+", reason: "QRC forum + Carlson: low-cross suspended family frees with less cross \u2014 setups.pdf placement first" }; } if (track === "wet") { return { lever: "qrc_scaling_track", action: "QRC scale wet/heavy (community): cross ~52\u201356% mid-band \xB7 lower left if RS hot \xB7 nose hold ~40\u201346% winged \xB7 conservative first laps before stagger upper band", reason: "Community (QRC + No Goats): soft tracks stack grip on suspended tread \u2014 cross ceiling stays low vs Pursuit" }; } return QRC_SCALING; } function toigoScalingForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "toigo_scaling_track", action: "Toigo scale slick/drying (scaling video + community): cross ~57\u201362% down from tacky \xB7 cradle mm before second cross cut \xB7 nose ~44\u201346% hold \xB7 do not copy QRC 52% window", reason: "Toigo scaling video: high-cross family still drops cross as track frees \u2014 cradle before washers" }; } if (track === "wet") { return { lever: "toigo_scaling_track", action: "Toigo scale wet/heavy (scaling video): cross mid ~61\u201363% conservative \xB7 lower left if RS overheats \xB7 cradle forward if push after nose/left within 0.5%", reason: "Toigo scaling video: moisture stacks wing + cross on offset ladder \u2014 nose/left before RF washers" }; } return TOIGO_SCALING; } function phantomScalingForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "phantom_scaling_track", action: "Phantom MINecon slick/drying (setup sheets + community): cross ~60\u201366% down from tacky 64\u201370 \xB7 cradle \xB12 in for nose before spindle cross \xB7 roll-speed chassis binds if cross too high when freed", reason: "Phantom public docs + community: high-cross MINecon still drops cross on slick \u2014 not QRC low-cross numbers" }; } if (track === "wet") { return { lever: "phantom_scaling_track", action: "Phantom MINecon wet (setup sheets): cross mid-64\u201368% conservative \xB7 cassette hole logged \xB7 wing rail small steps \u2014 explosive roll stacks quickly on soft 1/8 bank", reason: "Phantom MINecon public docs: wet/heavy \u2014 conservative cross + wing pairing on roll-speed chassis" }; } return PHANTOM_SCALING; } function slackCrossBandForTrack(track, isSenior) { if (track === "slick" || track === "drying") { return { lever: "slack_scaling_track", action: isSenior ? "Slack Pursuit slick/drying senior (doc + community): cross ~57\u201362% vs tacky 60\u201366 \xB7 nose ~46\u201347% \xB7 left ~57\u201358% \u2014 grip adds cross ceiling; take cross out before stagger" : "Slack Pursuit slick/drying junior (doc + community): cross ~55\u201360% vs tacky 57\u201363 \xB7 nose ~47\u201348% \xB7 left ~54\u201356%", reason: "Slack Pursuit public guide + No Goats: high-cross family frees with less cross built in" }; } if (track === "wet") { return { lever: "slack_scaling_track", action: isSenior ? "Slack Pursuit wet senior: cross mid-61\u201364% conservative \xB7 seat 8.5\u20139.5 in off axle \xB7 less cross built in if driver heavy or track soft" : "Slack Pursuit wet junior: cross mid-58\u201362% \xB7 seat height critical \u2014 heavier driver wants less cross baseline", reason: "Slack Pursuit public guide: wet stacks grip on Pursuit high-cross cage" }; } return isSenior ? SLACK_SCALING_SR : SLACK_SCALING_JR; } function qrcDirectionForTrack(track) { if (track === "slick" || track === "drying") { return [ { lever: "qrc_direction_track", action: "QRC slick/drying at-track: RR hop \u2192 cross down 0.5% or RR in \xB7 take rear stagger out before wing \xB7 fast-until-hot-then-tight \u2192 lower cross not add stagger \xB7 never Pursuit 61%+", reason: "Carlson QRC forum + community: QRC signature on freed suspended tread" }, QRC_DIRECTION ]; } return [QRC_DIRECTION, COMMUNITY_LOOSEN]; } function toigoDirectionForTrack(track) { if (track === "slick" || track === "drying") { return [TOIGO_RESPONSE, COMMUNITY_TIGHTEN, TOIGO_DIRECTION]; } if (track === "wet") { return [TOIGO_RESPONSE, COMMUNITY_LOOSEN, TOIGO_DIRECTION]; } return [TOIGO_RESPONSE, TOIGO_DIRECTION, COMMUNITY_LOOSEN]; } var SLACK_PHILOSOPHY = { lever: "slack_philosophy", action: "Slack Pursuit/Reactor/Xpect/Elevate: seat height + position first (dirt 8.5\u20139.5 in off rear axle) \u2192 published nose/left/cross bands \u2192 align \u2192 tires \u2192 cross/stagger fine-tune \u2014 high-cross family; grip adds cross ceiling", reason: "Slack Pursuit public guide: seat height is critical \u2014 heavier driver or more grip usually wants less cross built into baseline" }; var SLACK_SCALING_JR = { lever: "slack_scaling", action: "Scale dirt junior (Slack Pursuit doc): nose 47\u201348% \xB7 left 54\u201357.5% \xB7 cross 57\u201363% \xB7 RF camber -2 to -3\xB0 \xB7 LF +0.5 to +1\xB0 \u2014 community may run 64\u201368% when tires are right", reason: "Slack Pursuit public guide: high-cross family \u2014 add cross to four-wheel drift then back ~2%" }; var SLACK_SCALING_SR = { lever: "slack_scaling_sr", action: "Scale dirt senior (Slack Pursuit doc): nose 46\u201347% \xB7 left 57\u201359% \xB7 cross 60\u201366% \xB7 RF -2.25 to -3\xB0 \xB7 LF +0.25 to +1\xB0", reason: "Slack Pursuit public guide: senior left % rises vs junior \u2014 cross ceiling rises with grip" }; var SLACK_GEOMETRY = { lever: "slack_geometry", action: "Slack assembly (Pursuit doc): RF lead front hole dirt \xB7 rear cassette bottom hole dirt \xB7 pills at 0 \xB7 RR 1/8\u20133/16 in off frame rail \xB7 ~39 in rear track \xB7 wheels close to spindle without contact", reason: "Slack Pursuit public guide: assembly settings are baseline \u2014 bound seat binds offset chassis" }; var SLACK_RIDE_HEIGHT = { lever: "slack_ride_height", action: "Slack ride height (Pursuit doc): seat bottom 8.5\u20139.5 in above rear axle dirt \xB7 junior seat centered on steering post \xB7 senior centered or slightly left of upright \xB7 seat snug on rubber washers only", reason: "Slack Pursuit public guide: seat height changes effective cross and nose \u2014 measure every rebuild" }; var SLACK_TUNING = { lever: "slack_tuning", action: "Slack quick tune (Pursuit doc): entry push \u2192 more rear stagger, more nose, less cross if RF overloaded \xB7 entry loose \u2192 less stagger, more cross if LR unloaded \xB7 exit push \u2192 more cross if RR loaded \xB7 exit loose \u2192 less stagger, more left", reason: "Slack Pursuit public directional list \u2014 diagnose entry vs exit before stacking cross and stagger" }; var QRC_TOIGO_WINGED_AXLE = { lever: "outlaw_qrc_toigo_axle", action: "Rear axle slot (QRC + Toigo floater): forward helps rotation on short/banked 1/8 \xB7 rearward adds nose % and can tighten entry \xB7 axle down \u2248 +1% cross per turn \xB7 typical 4+ holes each direction \u2014 log hole every scale", reason: "Community (No Goats + Carlson winged outlaw): axle fore/aft is a major lever on floating cage karts \u2014 moves nose and cross together" }; var QRC_TOIGO_WINGED_SEAT = { lever: "outlaw_qrc_toigo_seat", action: "Nose via seat/cradle (QRC + Toigo): pushing \u2192 seat/cradle forward \xB7 loose entry \u2192 seat back \xB7 winged outlaws need ~40\u201348% nose \u2014 seat against cage rear often reads ~34\u201339% nose (too low)", reason: "Community (Carlson + No Goats): fix nose/rear on scales before cross or stagger \u2014 seat position is the primary scale lever" }; var QRC_TOIGO_WINGED_STAGGER = { lever: "outlaw_qrc_toigo_stagger", action: "Stagger phase (QRC + Toigo): rear stagger = middle rotation \xB7 front stagger jacks cross \xB7 remounting different stagger changes cross (forum: up to ~5% swing) \u2014 scale on race-day tires and log circumference", reason: "Community (No Goats + Toigo scaling video): stagger is not independent of cross on inch-stagger outlaws" }; var QRC_TOIGO_WINGED_RF = { lever: "outlaw_qrc_toigo_rf", action: "Narrow RF + caster (QRC + Toigo offset): run RF tight to spindle/shock as designed \xB7 ~2\xB0 caster split \xB7 RF ~8\u201313\xB0 by grip \xB7 offset cage needs more caster than flat-kart clone \u2014 wide RF invalidates QRC geometry", reason: "Community (Carlson QRC + No Goats): narrow RF + caster is part of offset outlaw baseline \u2014 not LO206 width math" }; var TOIGO_PHILOSOPHY = { lever: "toigo_philosophy", action: "Toigo Stealth/Wraith/Elevate: scales workflow = nose (cradle) \u2192 left (seat/ballast) \u2192 cross (RF washers) \u2192 stagger last \u2014 offset chassis purpose-built for outlaw; cross is fine-tune after nose/left are within ~0.5%", reason: "Toigo scaling video (Scaling and Adjustments): left and nose move CG; cross moves diagonal bite only" }; var TOIGO_BUILD = { lever: "toigo_build_order", action: "Toigo outlaw build order (conversion video): receivers + cage \u2192 body panels \u2192 wing \u2192 seat cradle \u2192 seat + harness \u2192 bolt check \u2014 never mount panels before cage/receivers square", reason: "Toigo public assembly video: cradle/seat last so axle and cage geometry stay true before scaling" }; var TOIGO_SCALING = { lever: "toigo_scaling", action: "Toigo scale targets (scaling video + service): nose ~44\u201348% winged (cradle forward from cage rear) \xB7 left ~52\u201358% junior / ~58\u201361% adult open \xB7 cross ~59\u201366% (video example ~61\u201362%) \xB7 get nose/left within ~0.5% before cross washers", reason: "Toigo scaling video: high-cross outlaw family \u2014 example mid-band ~61\u201362% cross once nose/left set" }; var TOIGO_SCALING_ORDER = { lever: "toigo_scaling_order", action: "Toigo scale sequence (video): 1) slide cradle for nose % \xB7 2) seat position + low lead on seat (not chassis/cradle) for left % \xB7 3) RF spindle washers for cross \xB7 4) mount race stagger and re-check cross \xB7 log cradle mm every change", reason: "Toigo scaling video: ballast on seat low \u2014 mounting weight to chassis/cradle influences flex and false scale reads" }; var TOIGO_SEAT = { lever: "toigo_seat", action: "Toigo seat cradle: mount seat to cradle \xB7 slide fore/aft for nose % and cross \xB7 ballast low on seat/back \u2014 never loose shot in frame \xB7 cradle must flex, not bind on struts \xB7 rubber washers only \u2014 snug not bound", reason: "Toigo product docs + scaling video: low CG ballast on seat \u2014 weight high in cage hurts rotation" }; var TOIGO_FRONT_END = { lever: "toigo_front_end", action: "Toigo front end (setup guide + video): manufacturer caster/camber baseline first \xB7 RF washer cross changes require toe/camber re-check \xB7 camber: RF more negative lowers cross \xB7 LF more positive adds cross", reason: "Toigo public baseline + No Goats: front-end moves change cross \u2014 align and re-scale as a pair" }; var TOIGO_CROSS_METHOD = { lever: "toigo_cross_method", action: "Toigo cross at scale (video): RF kingpin washers \u2014 washer above spindle = RF heavier = cross up \xB7 one washer \u2248 document on your scales at home \xB7 lead LR-side of seat raises cross \xB7 LF-area lead lowers cross", reason: "Toigo scaling video + kart cross mechanics: washers are race-night cross tool only after nose/left locked" }; var TOIGO_STAGGER_CROSS = { lever: "toigo_stagger_cross", action: "Toigo stagger effect (video + community): changing rear stagger changes cross when remounted \u2014 scale on tires you will race \xB7 7/8 in vs 1-1/2 in rear stagger can swing cross several % \xB7 set stagger then re-read cross", reason: "Toigo scaling video + forum: stagger and cross interact \u2014 never trust cross from last week's tire set" }; var TOIGO_LEFT_SIDE = { lever: "toigo_left_side", action: "Toigo left side (video): left % sets how hard RS tires work in corner \xB7 too much left = RS won't work \xB7 too little = loose off \xB7 adult open often ~58\u201361% with nose ~44\u201348% on offset Toigo", reason: "Toigo scaling video: left and nose together set CG window \u2014 cross dead-zones exist (~2% band where washers do little)" }; var TOIGO_LOGGING = { lever: "toigo_logging", action: "Toigo scale log (video): record date \xB7 driver \xB7 PSI \xB7 stagger \xB7 nose/left/cross every scale and after every session \u2014 compare start vs end of night to learn what moved", reason: "Toigo scaling video: documented scale history beats guessing which lever changed handling" }; var TOIGO_RESPONSE = { lever: "toigo_response", action: "Toigo adjustment response (video + community): entry push \u2192 cradle forward / nose up \xB7 loose entry \u2192 cradle back \xB7 tight center after cross cut \u2192 cradle mm not second cross \xB7 exit loose \u2192 cross up ~0.5% if LR unloaded \xB7 four-wheel drift \u2192 cross up + check left", reason: "Toigo scaling video + No Goats: floating cage responds to cradle before hero stagger stacks" }; var TOIGO_DIRECTION = { lever: "toigo_direction", action: "Toigo at-track: tight center after cross drop \u2192 cradle mm before second cross \xB7 loose entry \u2192 nose up + forward axle \xB7 pushing \u2192 cradle forward \xB7 exit loose \u2192 cross up 0.5% if LR unloaded \xB7 do not copy QRC 52% cross on Toigo", reason: "Toigo scaling video + community: high-cross Toigo vs low-cross QRC \u2014 different families" }; var TOIGO_SHOCKS = { lever: "toigo_shocks", action: "Toigo shocks (community + service): entry push \u2192 nose/cross/cradle before shock \xB7 exit loose \u2192 RF rebound / LR comp band \xB7 slick = soften RR-side after cross cut if exit binds \xB7 log valving with scale sheet", reason: "Community (Toigo service + No Goats): shock fine-tune after nose/left/cross family set on offset ladder" }; var QRC_REAR_GEOMETRY = { lever: "qrc_rear_geometry", action: "QRC rear geometry (setups.pdf + forum): cassette hole logged every night \xB7 RR hub ~\u22643/4 in off cassette (community ~1/4 in tight) \xB7 rear axle level side-to-side \xB7 diagonal check after any RR spacing move \u2014 hop when track grips = spacing or cross", reason: "QRC public build guide + Carlson forum: RR spacing is baseline geometry, not race-night cross tool" }; var PHANTOM_PHILOSOPHY = { lever: "phantom_philosophy", action: "Phantom MINecon Outlaw: speedway-derived roll speed \u2014 start class setup sheet on phantomchassis.com \u2192 caster blocks + seat cradle + wing rail; chassis rotates faster than many outlaws so cross timing matters", reason: "Phantom MINecon public docs: explosive roll vs competitors \u2014 do not copy QRC low-cross numbers" }; var PHANTOM_GEOMETRY = { lever: "phantom_geometry", action: "Phantom geometry (MINecon doc): adjustable caster/camber blocks \xB7 rear cassettes for lead/ride height \xB7 ~2\xB0 caster split \xB7 RF caster 8\u201313\xB0 by grip \xB7 Mini Outlaw 2.0 adjustable lead rear hangers", reason: "Phantom public docs: high cross preloads RF \u2014 excessive caster split = lazy push in center" }; var PHANTOM_SCALING = { lever: "phantom_scaling", action: "Phantom MINecon scaling (setup sheets): nose 45\u201346% \xB7 left 58.5\u201361% \xB7 cross 64\u201370% \xB7 seat cradle \xB12 in fore/aft \xB7 X-Factor spindles add cross capacity at 62%+", reason: "Phantom public setup sheets: spindle choice changes cross ceiling \u2014 scale in same gear every time" }; var PHANTOM_SEAT_CRADLE = { lever: "phantom_seat_cradle", action: "Phantom seat cradle (doc): independent \xB12 in fore/aft slide \xB7 seat+cradle removes as one unit \xB7 cradle move for nose before spindle cross tricks", reason: "Phantom MINecon public docs: cradle adjustability is a design feature \u2014 rigid mount skips a primary lever" }; var PHANTOM_WING = { lever: "phantom_wing", action: "Phantom wing (MINecon doc): adjustable aluminum wing rails + strut system \xB7 small angle/rail steps; roll-speed chassis responds quickly to wing + cross on 1/8 high-bank", reason: "Phantom MINecon public docs: lightweight mini-outlaw wing is primary aero lever \u2014 log rail with angle" }; var PHANTOM_RIDE_HEIGHT = { lever: "phantom_ride_height", action: "Phantom ride height: rear axle cassettes set lead and height \xB7 Mini Outlaw 2.0 lead hangers \u2014 establish baseline cassette hole before cross chasing", reason: "Phantom public docs: cassette lead changes handling phase \u2014 log hole like axle slot" }; var QRC_PHILOSOPHY = { lever: "qrc_philosophy", action: "QRC outlaw: low-cross suspended chassis \u2014 start qrckarts.com qrc-setups.pdf + assembly playlist \u2192 set component positions per QRC \u2192 scale nose/left/cross in low window \u2192 on-track tune \u2014 not Pursuit high-cross math", reason: "QRC phone support + Advanced Racing Concepts: QRC gives placement baselines; scale % comes from setups.pdf + local track dial-in" }; var QRC_SETUPS_PDF = { lever: "qrc_setups_pdf", action: "QRC setups.pdf (public doc): starting geometry and placement reference \u2014 pair with QRC YouTube assembly playlist \xB7 QRC does not publish one national scale sheet; setups.pdf + positioning gets you close per QRC dealers", reason: "QRC public doc (setups.pdf, forum-ref): manufacturer WHERE before HOW MUCH \u2014 verify at your banking with QRC racers" }; var QRC_POSITIONING = { lever: "qrc_positioning", action: "QRC assembly order (build guide): floorpan \u2192 front end \u2192 rear axle cassette/slot \u2192 seat last \xB7 kingpin adjuster sets spindle height \xB7 RR hub spacing ~\u22643/4 in off cassette (community ~1/4 in tight) \xB7 confirm hub spacing before cross experiments", reason: "QRC public build guide + Carlson forum: RR too far out causes hop when track grips \u2014 spacing is part of baseline" }; var QRC_SQUARING = { lever: "qrc_squaring", action: "QRC alignment (setups.pdf + No Goats): level scales \xB7 RF toe 0 first \xB7 square LF ~1/16 in out \xB7 rear axle level side-to-side \xB7 diagonal kingpin check \xB7 log cassette hole \u2014 re-check toe after any spindle washer move", reason: "QRC public baseline: suspended rear still needs square geometry before psi or cross chasing" }; var QRC_FRONT_END = { lever: "qrc_front_end", action: "QRC front end (doc + Carlson): narrow RF wheel/tire mandatory \xB7 caster/camber knuckle for quick changes \xB7 center caster blocks per QRC baseline \xB7 wide RF throws QRC setups away \xB7 LF camber ~+0.5 to +0.75\xB0 \xB7 RF ~-1.5 to -1.75\xB0 community band", reason: "Carlson Motorsports QRC forum: excessive caster vs flat kart + centered blocks \u2014 wide RF invalidates front baseline" }; var QRC_CASTer = { lever: "qrc_caster", action: "QRC caster (Carlson + product doc): center caster blocks on knuckle per QRC baseline \xB7 more caster than flat-kart clone \xB7 caster split ~2\xB0 \xB7 RF ~10\u201312\xB0 dirt start band when measured \xB7 decrease caster if kart binds on heavy track", reason: "QRC caster/camber knuckle product + Carlson: QRC expects higher caster \u2014 blocks centered unless track demands split tweak" }; var QRC_RIDE_HEIGHT = { lever: "qrc_ride_height", action: "QRC ride height / rake (suspended + community): rear axle up/down on cassette changes cross (~1 turn \u2248 1% community ref) \xB7 raise rear axle (lower chassis) frees \xB7 lower adds grip \xB7 set rake baseline from setups.pdf before night", reason: "Community (No Goats on floater karts): axle height is cross lever on QRC \u2014 log hole and height every scale" }; var QRC_SCALING = { lever: "qrc_scaling", action: "QRC scale window (community + setups.pdf): cross ~47\u201356% (52\u201356% target when track grips) \xB7 left ~54\u201358% \xB7 nose ~40\u201346% with wing (46% flat-kart habit \u2014 35% nose too low per Carlson) \xB7 RR hop = cross too high", reason: "QRC forum (47\u201353% seen) + Carlson: low-cross suspended family \u2014 not Toigo/ Pursuit 61%+" }; var QRC_NOSE = { lever: "qrc_nose", action: "QRC nose (Carlson winged outlaw): seat forward from cage rear until nose ~40\u201346% \xB7 34\u201339% nose usually seat too far back \xB7 move seat toward steering post before raising cross to fix push", reason: "Carlson + winged outlaw forum: adequate nose before cross \u2014 wide RF needs even more nose than flat kart" }; var QRC_SUSPENDED = { lever: "qrc_suspended", action: "QRC suspended chassis (community): set per QRC placement sheet first \xB7 scale confirms nose/left/cross family \xB7 on-track adjust RR hop (cross down) / push (nose up, axle forward) \u2014 chasing cross before geometry rarely works", reason: "Community (suspended outlaw + QRC owners): placement-first workflow \u2014 scale validates, track confirms" }; var QRC_STAGGER = { lever: "qrc_stagger", action: "QRC stagger (1/8 mi community): rear ~3/4\u20131.25 in (Knoxville banked ~7/8 in when pushing) \xB7 front ~1.5 in \xB7 fix nose/rear before stagger \xB7 1.75 in rear often high unless flat/tight \xB7 stagger change remounts cross", reason: "QRC forum + No Goats: middle-phase lever after low-cross family set" }; function qrcStaggerPrior(track) { if (track === "slick" || track === "drying") { return { lever: "qrc_stagger", action: "QRC stagger slick (community): rear ~3/4\u20131 in \xB7 front ~1.25\u20131.5 in \u2014 take stagger out before wing; high stagger + low-cross QRC causes RR hop when track grips", reason: "QRC forum consensus: slick = less rear stagger before cross changes on suspended QRC" }; } return QRC_STAGGER; } var QRC_DIRECTION = { lever: "qrc_direction", action: "QRC at-track signature: RR hop mid-turn \u2192 cross too high or RR too far out \xB7 entry push \u2192 nose up, seat forward, axle forward \xB7 fast until tires in then tight \u2192 lower cross not add stagger \xB7 never copy Toigo 61%+ cross on QRC", reason: "Carlson QRC forum: RR hop is QRC tell for over-cross \u2014 lower cross or RR in before Pursuit-style numbers" }; var QRC_VS_TOIGO = { lever: "qrc_vs_toigo", action: "QRC vs Toigo (brand split): QRC = low-cross ~52\u201356% suspended \xB7 Toigo = high-cross ~59\u201366% offset ladder \xB7 same seat/axle/stagger levers, different cross ceiling \u2014 do not interchange scale sheets", reason: "Community + both manufacturers: most common outlaw mistake is Pursuit/Toigo cross on QRC (RR hop)" }; var ULTRAMAX_PHILOSOPHY = { lever: "ultramax_philosophy", action: "Ultramax Rival/Evolve outlaw: model-year setup sheet first \u2014 Evolve vs Rival differ in cross/stagger window; offset cage uses cross + stagger like other high-cross outlaws", reason: "Ultramax public baseline (limited vs Slack): confirm generation sheet before Pursuit numbers" }; var ULTRAMAX_SCALING = { lever: "ultramax_scaling", action: "Ultramax start (community + dealer notes): cross 59\u201366% \xB7 left 52\u201358% \xB7 nose mid-40s % \xB7 rear stagger 1\u20132.5 in by track \u2014 verify Rival vs Evolve", reason: "Community (high-cross outlaw family): Ultramax closer to Pursuit than QRC low-cross" }; var CHAOS_PHILOSOPHY = { lever: "chaos_philosophy", action: "Chaos/Carlson T-18 tread outlaw: tread RR sidewall flex limits cross ceiling \u2014 often high 50s/low 60s vs prepped slick fields; tire duro/prep dominate", reason: "Carlson/Vector tread positioning: tread flex acts like extra spring \u2014 not Pursuit 68% blindly" }; var CHAOS_SCALING = { lever: "chaos_scaling", action: "Chaos tread setup (Vector DO + community): cross high 50s\u2013low 60s \xB7 left ~57% \xB7 nose mid-40s \xB7 alternate tread ref CW ~53\u201354% \xB7 stagger conservative on slick", reason: "Carlson Vector tread guide + community: tread programs sit between QRC low-cross and Pursuit high-cross" }; var BUILDER_PRIORS = { slack: (track, vc2) => { const isSenior = /senior|sr|375|390|425|450|500|open|unrestricted/i.test( `${vc2.car_class || ""} ${vc2.class_name || ""}` ); return [ SLACK_PHILOSOPHY, WINGED_GENERAL_PHILOSOPHY, SLACK_RIDE_HEIGHT, SLACK_GEOMETRY, COMMUNITY_SQUARING, COMMUNITY_GEOMETRY, slackCrossBandForTrack(track, isSenior), COMMUNITY_NOSE_SEAT, COMMUNITY_CROSS_MECHANICS, ...wingedOutlawTrackCore(track, "high"), SLACK_TUNING ]; }, toigo: (track) => [ TOIGO_PHILOSOPHY, TOIGO_BUILD, TOIGO_SCALING_ORDER, toigoScalingForTrack(track), TOIGO_SEAT, TOIGO_LEFT_SIDE, TOIGO_CROSS_METHOD, TOIGO_STAGGER_CROSS, TOIGO_FRONT_END, QRC_TOIGO_WINGED_SEAT, QRC_TOIGO_WINGED_AXLE, QRC_TOIGO_WINGED_RF, crossPrior(track, "high"), leftSideForTrack(track), staggerPrior(track), QRC_TOIGO_WINGED_STAGGER, psiForTrack(track), wingForTrack(track), rideHeightForTrack(track), rideHeightAxleForTrack(track), ...toigoDirectionForTrack(track), TOIGO_SHOCKS, TOIGO_LOGGING, COMMUNITY_PHASE, QRC_VS_TOIGO, track === "drying" ? OUTLAW_DRYING_TRANSITION : null ].filter(Boolean), phantom: (track) => [ PHANTOM_PHILOSOPHY, WINGED_GENERAL_PHILOSOPHY, PHANTOM_SEAT_CRADLE, phantomScalingForTrack(track), PHANTOM_GEOMETRY, PHANTOM_RIDE_HEIGHT, COMMUNITY_SQUARING, COMMUNITY_NOSE_SEAT, COMMUNITY_CROSS_MECHANICS, crossPrior(track, "high"), leftSideForTrack(track), staggerPrior(track), psiForTrack(track), PHANTOM_WING, wingForTrack(track), rideHeightAxleForTrack(track), ...directionPriorsForTrack(track), COMMUNITY_PHASE, track === "drying" ? OUTLAW_DRYING_TRANSITION : null ].filter(Boolean), qrc: (track) => [ QRC_PHILOSOPHY, QRC_SETUPS_PDF, QRC_POSITIONING, QRC_SQUARING, QRC_REAR_GEOMETRY, QRC_FRONT_END, QRC_CASTer, QRC_RIDE_HEIGHT, qrcScalingForTrack(track), QRC_NOSE, QRC_SUSPENDED, QRC_TOIGO_WINGED_SEAT, QRC_TOIGO_WINGED_AXLE, crossPrior(track, "low"), leftSideForTrack(track), qrcStaggerPrior(track), QRC_TOIGO_WINGED_STAGGER, QRC_TOIGO_WINGED_RF, psiForTrack(track), wingForTrack(track), ...qrcDirectionForTrack(track), COMMUNITY_PHASE, QRC_VS_TOIGO, track === "drying" ? OUTLAW_DRYING_TRANSITION : null ].filter(Boolean), ultramax: (track) => [ ULTRAMAX_PHILOSOPHY, WINGED_GENERAL_PHILOSOPHY, ULTRAMAX_SCALING, COMMUNITY_NOSE_SEAT, COMMUNITY_GEOMETRY, COMMUNITY_SQUARING, COMMUNITY_CROSS_MECHANICS, ...wingedOutlawTrackCore(track, "high"), COMMUNITY_PHASE ], chaos: (track) => [ CHAOS_PHILOSOPHY, WINGED_GENERAL_PHILOSOPHY, CHAOS_SCALING, COMMUNITY_NOSE_SEAT, COMMUNITY_GEOMETRY, COMMUNITY_CROSS_MECHANICS, ...wingedOutlawTrackCore(track, "low"), COMMUNITY_PHASE ] }; var PHILOSOPHY_LINES = { slack: { slick: "Slack Pursuit slick: high-cross family drops cross ~2\u20134% from tacky \u2014 seat height 8.5\u20139.5 in off axle before washer cross", wet: "Slack Pursuit wet: conservative cross mid-band \u2014 heavier driver or soft track wants less cross built into baseline", default: SLACK_PHILOSOPHY.action }, toigo: { slick: "Toigo slick: cradle mm before second cross cut \u2014 high-cross ~57\u201362% band, never QRC 52% window", wet: "Toigo wet: nose \u2192 left \u2192 cross sequence on scales \xB7 conservative 61\u201363% cross first laps", default: TOIGO_PHILOSOPHY.action }, phantom: { slick: "Phantom MINecon slick: roll-speed chassis \u2014 drop cross from tacky 64\u201370 band \xB7 cradle move before spindle washers", default: PHANTOM_PHILOSOPHY.action }, qrc: { slick: "QRC slick: low-cross ~50\u201354% \xB7 take stagger out before wing \xB7 RR hop = cross too high for suspended tread", wet: "QRC wet: cross stays 52\u201356% ceiling \u2014 setups.pdf placement first, never Pursuit/Toigo 61%+", default: QRC_PHILOSOPHY.action }, ultramax: { default: ULTRAMAX_PHILOSOPHY.action }, chaos: { slick: "Chaos/Carlson tread slick: cross high 50s \u2014 tread flex limits ceiling vs prepped slick fields", default: CHAOS_PHILOSOPHY.action } }; function isOutlawDeepBuilder(mfrKey) { return DEEP_BUILDERS.has(mfrKey); } function outlawGenericWingedPriors(trackState, opts = {}) { const track = normalizeTrack2(trackState); const family = opts.crossFamily === "low" ? "low" : "high"; const priors = [ WINGED_GENERAL_PHILOSOPHY, COMMUNITY_CROSS_MECHANICS, COMMUNITY_NOSE_SEAT, COMMUNITY_SQUARING, COMMUNITY_GEOMETRY, ...wingedOutlawTrackCore(track, family), COMMUNITY_PHASE ]; return withCaveat(priors).slice(0, opts.maxPriors ?? 14); } function outlawKartChassisPriors(mfrKey, enrichedVc = {}, trackState, opts = {}) { const maxDefault = mfrKey === "qrc" || mfrKey === "toigo" ? 20 : 18; const max = opts.maxPriors ?? maxDefault; const fn = BUILDER_PRIORS[mfrKey]; if (!fn) return []; const track = normalizeTrack2(trackState); const priors = fn(track, enrichedVc); return withCaveat(priors).slice(0, max); } function pickOutlawPhilosophyLine(mfrKey, trackState) { const track = normalizeTrack2(trackState); const lines = PHILOSOPHY_LINES[mfrKey]; if (!lines) return WINGED_GENERAL_PHILOSOPHY.action; if ((track === "slick" || track === "drying") && lines.slick) return lines.slick; if (track === "wet" && lines.wet) return lines.wet; return lines.default; } // scripts/lib/experimental/microSprintNonHyperKnowledge.mjs var MICRO_NON_HYPER_CAVEAT = "Public baseline from manufacturer documentation and community knowledge \u2014 validate with your own data and specific chassis setup."; var DEEP_BUILDERS2 = /* @__PURE__ */ new Set(["d1", "ten_j", "stallard", "pace", "sawyer", "factor1"]); function normalizeTrack3(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; return "normal"; } function withCaveat2(priors) { return priors.map((p) => ({ ...p, caveat: MICRO_NON_HYPER_CAVEAT })); } var TRAILING_PHILOSOPHY = { lever: "micro_trailing_philosophy", action: "Trailing-arm 600 micro order: square rear (12-1/2 in LR arm + rod tool) \u2192 time birdcages \u2192 square front \u2192 ride height to torsion bar center \u2192 stagger \u2192 wing \u2192 psi \u2014 arm length changes bar size vs Hyper wishbone", reason: "Hyper Setup Manual (trailing-arm note): RTS/PMP/Stallard use forward-tilted birdcage timing \u2014 not Hyper X-series wishbone shackle numbers" }; var TRAILING_SQUARING_REAR = { lever: "micro_trailing_squaring_rear", action: "Rear square (PMP/GRE6 + Hyper trailing-arm ref): LR torsion arm 12-1/2 in spline-to-rod-end \xB7 7/16 in rod through heims on 2 in blocks \xB7 rod to front of rear torsion tube 10-3/16\u201310-1/4 in \xB7 axle back edge to rear tube 12-3/16\u201312-1/4 in both sides", reason: "PMP 600 setup sheet (manufacturer): trailing-arm squaring references \u2014 1/32 in error reads on 1/6\u20131/8 mi tracks" }; var TRAILING_BIRDCAGE = { lever: "micro_trailing_birdcage", action: "Birdcage timing (Hyper trailing-arm ref): top of carrier tilted forward ~3\xB0 \u2014 upper rod-end distance ~5/16 in greater than lower (distance x vs y) \xB7 floater caliper: keep carrier level instead", reason: "Hyper Setup Manual: trailing-arm (RTS/PMP/Stallard) birdcage timing differs from Hyper wishbone level carrier" }; var TRAILING_SQUARING_FRONT = { lever: "micro_trailing_squaring_front", action: "Front square (PMP/GRE6): on 2 in blocks measure front upright tube to back of front axle 6\u20136-1/8 in (5\u20135-1/8 in on 2006 & older) \xB7 target wheelbase ~60-1/2 in \xB7 front offset ~3/4 in right via panhard", reason: "PMP 600 setup sheet (manufacturer): front upright-to-axle is the Stallard/PMP family reference \u2014 not Hyper X7 shackle holes" }; var TRAILING_RIDE_HEIGHT = { lever: "micro_trailing_ride_height", action: "Ride height (PMP/GRE6, no driver, ~2 gal fuel): measure ground to torsion bar center \xB7 LF coil 3.50\u20133.75 in / LF bar 10.50 in \xB7 RF coil 4.25\u20134.50 / RF bar 11.25\u201311.50 \xB7 LR bar 7.50\u20137.75 \xB7 RR bar 8.75\u20139.00 in", reason: "PMP 600 setup sheet (manufacturer): bar-center measurement \u2014 rack height changes vs other brands" }; var TRAILING_BARS = { lever: "micro_trailing_bars", action: "Torsion start (PMP/GRE6): LF .550 solid or .725 hollow \xB7 RF .575 solid \xB7 LR .725 \xB7 RR .725\u2013.750 \xB7 LF coil 120\u2013125 lb / RF 140\u2013150 lb if equipped \xB7 verify arm length before copying Hyper bar sizes", reason: "PMP 600 setup sheet (manufacturer): bar diameter meaningless without matching arm length \u2014 use builder sheet first" }; var TRAILING_SCALING = { lever: "micro_trailing_scaling", action: "Scale (Hyper manual community ref for micro): rear weight bias ~62\u201365% \xB7 crossbite ~42\u201349% \xB7 left side via RR/LR hub spacing \xB7 heavy driver >220 lb add ~1/2\u20133/4 in to RR offset measurements", reason: "Hyper Setup Manual scaling section: applies as community start when builder sheet lacks scale % \u2014 validate on your car" }; function trailingStaggerPrior(track) { if (track === "slick" || track === "drying") { return { lever: "micro_trailing_stagger", action: "Stagger slick (600 micro community): rear ~4\u20137 in \xB7 take out before wing add \xB7 remounting tires changes cross \u2014 re-scale on race-day rubber", reason: "Community (600 micro + Hyper directional principles): less rear stagger tightens as track frees \u2014 not Hyper X7 PDF numbers verbatim" }; } if (track === "wet" || track === "greasy") { return { lever: "micro_trailing_stagger", action: "Stagger wet (600 micro community): rear ~7\u20139 in upper band if loose \xB7 2 in front blocks / 2-1/4 rear blocks common wet prep on trailing-arm cars", reason: "Community (PMP/GRE6 wet handling + micro norms): moisture stacks grip \u2014 conservative cross/stagger first laps" }; } return { lever: "micro_trailing_stagger", action: "Stagger tacky/normal (600 micro community): rear ~5\u20138 in on 10 in wheels \xB7 front stagger mostly ride-height/cross \u2014 log hot after laps 8\u201310 \xB7 1/4 in rear step per run", reason: "Community (600 micro): inch stagger primary middle lever on dirt micro \u2014 arm length affects effective bite" }; } var TRAILING_PSI = { lever: "micro_trailing_psi", action: "Psi (600 micro community): log cold/hot \xB7 RS often 6.5\u201310 band \xB7 LR 5\u20138 \xB7 more LR psi adds static weight AND reduces effective stagger \xB7 1/4 psi steps after compound correct", reason: "Community (Hyper manual psi principles): LR psi is both spring rate and weight lever on micro sprints" }; var TRAILING_DIRECTION_TIGHT = { lever: "micro_trailing_direction_tight", action: "Tighten entry (PMP/GRE6 ref): lower RR psi \xB7 RR in 1/2\u20131 in \xB7 less rear stagger \xB7 wing back 1\u20132 in \xB7 Jacobs inside/outside top+bottom \xB7 reduce LR shock rebound", reason: "PMP 600 setup sheet (manufacturer): entry tight list \u2014 one lever per run" }; var TRAILING_DIRECTION_LOOSE = { lever: "micro_trailing_direction_loose", action: "Loosen exit (PMP/GRE6 ref): raise front 4 coil turns / 1 torsion turn \xB7 less stagger \xB7 wing back \xB7 add LR weight \xB7 RF #0.5 rebound / LF #1 rebound shock valving band", reason: "PMP 600 setup sheet (manufacturer): exit/forward bite list \u2014 diagnose phase before stacking" }; var TRAILING_PHASE = { lever: "micro_trailing_phase_tuning", action: "Phase tuning (600 micro trailing-arm): entry push \u2192 RR psi down / RR in / wing back \xB7 mid \u2192 inch stagger \xB7 exit loose \u2192 front RH up / stagger out \xB7 tight in/loose off \u2192 entry fix this run, exit next run only (Factor 1 rule)", reason: "Community (PMP/GRE6 + Factor 1): trailing-arm micro \u2014 entry vs exit vs middle use different levers before bar turns" }; var TRAILING_WING = { lever: "micro_trailing_wing", action: "Wing (600 micro community): short track 12\u201315\xB0 \xB7 long track 8\u201311\xB0 \xB7 trailing edge ~4\u20136 in ahead of rear axle start \xB7 wing back 1\u20132 in tightens entry", reason: "Community (GRE6/PMP wing tips): angle + fore/aft move load together on winged 600 micro" }; var TRAILING_TRACK_CONDITIONS = { lever: "micro_trailing_track_state", action: "Track state (600 micro trailing-arm): wet/soft \u2192 taller blocks + more rear stagger + conservative cross \xB7 tacky \u2192 PMP/average sheet baseline \xB7 drying \u2192 take stagger out before wing/cross add \xB7 slick \u2192 less stagger + lower cross + wing back \u2014 log every session transition", reason: "Community (PMP/GRE6 + Hyper manual directional): trailing-arm micro responds to inch stagger and cross before bar turns on state change" }; var TRAILING_DRYING_TRANSITION = { lever: "micro_trailing_drying", action: "Drying transition (600 micro): rear stagger down 1/4\u20131/2 in per run before cross up \xB7 wing back 1 in \xB7 LR psi down 1/4 \xB7 if exit binds after entry fix, undo entry change first (Factor 1 phase rule)", reason: "Community (600 micro + Factor 1): tacky \u2192 drying often needs less rear stagger, not more cross \u2014 one lever per run" }; var TRAILING_DIRECTION_SLICK_EXIT = { lever: "micro_trailing_direction_slick", action: "Slick/freeing tighten (PMP/GRE6): less rear stagger to ~4\u20136 in \xB7 lower RR/LR psi \xB7 RR in 1/2 in \xB7 wing back + flatter angle \xB7 soften RR bar 1/2 turn before RF shock chase", reason: "PMP 600 setup sheet (manufacturer): slick path prioritizes stagger + psi before bar stacks on trailing-arm" }; var TRAILING_DIRECTION_WET_FIRST = { lever: "micro_trailing_direction_wet", action: "Wet/heavy first laps (600 micro community): hold cross mid-band \xB7 rear stagger upper 7\u20139 in if loose \xB7 2 in front / 2-1/4 rear blocks \xB7 Jacobs toward hole 1 \xB7 do not max stagger + max cross same heat", reason: "Community (PMP wet prep + micro norms): moisture stacks grip \u2014 conservative cross/stagger pairing on trailing-arm" }; function crossForTrailingTrack(track) { if (track === "slick") { return { lever: "micro_trailing_cross_slick", action: "Cross slick (600 micro community): target ~42\u201345% crossbite \xB7 rear weight bias hold ~62\u201365% \xB7 drop cross 0.5% before adding rear stagger when track frees", reason: "Hyper Setup Manual scaling (trailing-arm community ref): slick often wants lower cross vs tacky 44\u201349% window" }; } if (track === "drying") { return { lever: "micro_trailing_cross_drying", action: "Cross drying (600 micro community): cross down 0.5\u20131% step before stagger add \xB7 if RR unloads mid-turn, cross still high for current grip \u2014 log hot tire before second change", reason: "Community (600 micro): drying transition = less cross/stagger, not more wing first" }; } if (track === "wet") { return { lever: "micro_trailing_cross_wet", action: "Cross wet/heavy (600 micro community): cross ~44\u201347% mid-band \xB7 left side via hub spacing not flat-kart math \xB7 avoid high cross + max stagger when moisture stacks", reason: "Community (PMP/GRE6 + micro scaling): soft tracks want conservative cross before inch stagger chase" }; } return { lever: "micro_trailing_cross_tacky", action: "Cross tacky/normal (Hyper manual community ref): crossbite ~44\u201349% \xB7 rear weight ~62\u201365% \xB7 heavy driver >220 lb add ~1/2\u20133/4 in RR offset when scaling", reason: "Hyper Setup Manual scaling section: tacky-start band for trailing-arm micro \u2014 validate on your car" }; } function barsForTrailingTrack(track) { if (track === "slick" || track === "drying") { return { lever: "micro_trailing_bars_slick", action: "Bars slick (PMP/GRE6 + community): LF .550/.725 +1/2 turn \xB7 RF .575 +1/2 \xB7 LR .725 +1\u20131-1/2 \xB7 RR .725\u2013.750 +1\u20131-1/2 \xB7 coils +4\u20136 LF if equipped \xB7 less stagger before bar stack", reason: "PMP directional + Hyper manual (community trailing-arm ref): freeing track softens rear bar turns after stagger/psi" }; } if (track === "wet") { return { lever: "micro_trailing_bars_wet", action: "Bars wet (PMP/GRE6): hold RF .575 \xB7 LR .725 \xB7 RR .725\u2013.775 upper \xB7 LF .550/.725 baseline \xB7 pair with 2 in front / 2-1/4 rear blocks before extra bar turns", reason: "PMP 600 setup sheet (manufacturer): wet prep starts at block height \u2014 bar diameter still arm-length specific" }; } return TRAILING_BARS; } function rideHeightForTrack2(track) { if (track === "wet") { return { lever: "micro_trailing_ride_height_wet", action: "Ride height wet (PMP/GRE6): 2 in front blocks / 2-1/4 rear \xB7 measure ground to torsion bar center (no driver, ~2 gal fuel) \xB7 LR bar 7.75\u20138.0 / RR 9.0\u20139.25 in upper wet band", reason: "PMP 600 setup sheet + GRE6 wet prep (manufacturer/community): taller rear blocks before torsion turn chase" }; } if (track === "slick" || track === "drying") { return { lever: "micro_trailing_ride_height_slick", action: "Ride height slick/drying (600 micro community): hold PMP bar-center refs or +1/2 torsion turn all corners vs tacky \xB7 panhard in 1/2 in common tighten \xB7 drop stagger before lowering RH on freeing track", reason: "Community (PMP directional): trailing-arm slick path \u2014 stagger/psi before RH drop" }; } return TRAILING_RIDE_HEIGHT; } function psiForTrailingTrack(track) { if (track === "slick" || track === "drying") { return { lever: "micro_trailing_psi_slick", action: "Psi slick (600 micro community): LF/RF ~9 \xB7 LR 4.5\u20135.5 \xB7 RR 5.5\u20136.5 \xB7 take LR down 1/4 psi before RR up when tightening \xB7 log hot after laps 8\u201310", reason: "Community (Hyper manual psi principles): slick = lower LR band + less effective stagger from LR psi" }; } if (track === "wet") { return { lever: "micro_trailing_psi_wet", action: "Psi wet/heavy (600 micro community): LR 6\u20138 \xB7 RR 7\u201310 upper band if loose \xB7 RS stiffer loosens / LS stiffer tightens \xB7 1/4 psi steps only after compound correct", reason: "Community (600 micro + PMP wet norms): moisture allows higher rear psi band before shock chase" }; } return TRAILING_PSI; } function wingForTrailingTrack(track) { if (track === "slick" || track === "drying") { return { lever: "micro_trailing_wing_slick", action: "Wing slick (600 micro community): long track 8\u201310\xB0 \xB7 short 11\u201313\xB0 \xB7 wing back 1\u20132 in \xB7 trailing edge 4\u20136 in ahead of rear axle \u2014 angle + fore/aft together", reason: "Community (GRE6/PMP): freeing track = less wing before cross add on winged 600 micro" }; } if (track === "wet") { return { lever: "micro_trailing_wing_wet", action: "Wing wet (600 micro community): short track 13\u201315\xB0 \xB7 long 9\u201311\xB0 \xB7 wing forward if loose exit after stagger/cross conservative start", reason: "Community (600 micro wing norms): wet often wants more downforce after baseline stagger set" }; } return TRAILING_WING; } function shocksForTrailingTrack(track) { if (track === "slick" || track === "drying") { return { lever: "micro_trailing_shocks_slick", action: "Shocks slick (PMP/GRE6 + community): soften RR comp / stiffen RF comp common tighten \xB7 reduce LR rebound if entry tight \xB7 RF #0.5 rebound band on exit push \u2014 stabilize psi/stagger first", reason: "PMP setup sheet directional (manufacturer): shock fine-tune after stagger/psi on freeing trailing-arm micro" }; } if (track === "wet") { return { lever: "micro_trailing_shocks_wet", action: "Shocks wet (600 micro community): LR more rebound if loose entry \xB7 RF compression up if push \xB7 avoid full stiff stacks until blocks/stagger set \xB7 log valving with moisture session", reason: "Community (600 micro): wet prep \u2014 geometry and stagger before shock stacks on trailing-arm" }; } return null; } function directionPriorsForTrack2(track) { if (track === "slick") return [TRAILING_DIRECTION_SLICK_EXIT, TRAILING_DIRECTION_TIGHT, TRAILING_PHASE]; if (track === "drying") return [TRAILING_DRYING_TRANSITION, TRAILING_DIRECTION_TIGHT, TRAILING_PHASE]; if (track === "wet") return [TRAILING_DIRECTION_WET_FIRST, TRAILING_DIRECTION_LOOSE, TRAILING_PHASE]; return [TRAILING_DIRECTION_LOOSE, TRAILING_DIRECTION_TIGHT, TRAILING_PHASE]; } function stallardTrackPrior(track) { if (track === "slick" || track === "drying") { return { lever: "stallard_slick_sheet", action: "Stallard slick (manufacturer EMi sheet): use Stallard slick/average setup sheet \u2014 lower cross/stagger vs tacky average \xB7 LF 11 in / RF 9.5 in front refs still apply after axle moves", reason: "Stallard public docs (Speedway EMi): manufacturer publishes separate slick/average references" }; } return { lever: "stallard_tacky_sheet", action: "Stallard tacky/average (manufacturer EMi sheet): average setup sheet baseline \xB7 bump rear stagger only after hot tire log \xB7 caster 15\u201320\xB0 built-in \u2014 adjust with toe/rods not spindle swap", reason: "Stallard public docs: average sheet is tacky-start before track frees" }; } function factor1ShocksForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "factor1_shocks_slick", action: "Factor 1 slick shocks (Chassis 101 + community): tight in \u2192 LF comp up / LR rebound up \xB7 tight off \u2192 undo entry fix first \xB7 RF stiff comp + soft RR comp common freeing-track tighten on trailing-arm", reason: "Factor 1 Chassis 101 (manufacturer) + community: phase diagnosis before exit stack on slick" }; } if (track === "wet") { return { lever: "factor1_shocks_wet", action: "Factor 1 wet shocks (Chassis 101): loose in \u2192 LF/RF comp down band \xB7 loose off \u2192 RF rebound up / LR comp up \xB7 verify steering 20% right bias before shock chase in moisture", reason: "Factor 1 Chassis 101 (manufacturer): wet often needs conservative entry before exit valving" }; } return FACTOR1_SHOCKS; } function sawyerTrackPriors(track) { const priors = []; if (track === "slick" || track === "drying") { priors.push({ lever: "sawyer_slick", action: "Sawyer 626 slick (community + FAQ sheet): less rear stagger vs tacky \xB7 wing back 1\u20132 in \xB7 use Sawyer wishbone sheet \u2014 do not copy Stallard 11/9.5 in or PMP trailing-arm RH", reason: "Community (600 micro wishbone): 626 slick path = stagger/wing before bar turns on Sawyer geometry" }); } else if (track === "wet") { priors.push({ lever: "sawyer_wet", action: "Sawyer 626 wet (community + FAQ sheet): rear stagger upper band if loose \xB7 verify Sawyer axle spacing diagram before cross experiments \xB7 wishbone arm length sets bar size", reason: "Sawyer public docs + community: moisture \u2014 confirm 626 spacing before PMP trailing-arm cross habits" }); } else { priors.push({ lever: "sawyer_tacky", action: "Sawyer 626 tacky (FAQ sheet): log hot stagger after laps 8\u201310 \xB7 wishbone scaling like Hyper philosophy but Sawyer arm lengths \u2014 never copy Hyper X7 bar diameters", reason: "Sawyer public docs (manufacturer): 626 tacky baseline from FAQ setup sheet" }); } return priors; } function trailingArmTrackCore(track) { return [ TRAILING_TRACK_CONDITIONS, crossForTrailingTrack(track), rideHeightForTrack2(track), barsForTrailingTrack(track), trailingStaggerPrior(track), psiForTrailingTrack(track), wingForTrailingTrack(track), shocksForTrailingTrack(track), ...directionPriorsForTrack2(track) ].filter(Boolean); } var D1_PHILOSOPHY = { lever: "d1_philosophy", action: "D1/Driven Performance: assemble + square per Driven rear-axle PDF before scale experiments \u2014 seminar book + Dialed app cover squaring, setup theory, shocks \xB7 trailing-arm family like PMP but D1-specific arm lengths", reason: "D1 Driven public docs: placement and squaring before setup theory \u2014 not Hyper X-series PDF" }; var D1_REAR_SQUARE = { lever: "d1_rear_square", action: "Rear square (D1 PDF): LR torsion arm 12.5 in centerline-to-centerline with 7/16 in steel rod tool on 2 in blocks \xB7 time birdcages to 90\xB0 on 4 in axle blocks \xB7 Jacobs ladder + radius rods before ride height", reason: "D1 Driven rear squaring guide (manufacturer): rod-through-heims method is the D1 baseline anchor" }; var D1_VS_TRAILING = { lever: "d1_trailing_family", action: "D1 geometry: trailing-arm micro \u2014 use D1/Driven squaring PDF first \xB7 PMP/GRE6 numbers are close community cross-check \xB7 do not copy Hyper wishbone block/bar sheet onto D1", reason: "D1 + Hyper manual: D1 is trailing-arm family \u2014 Hyper X7 numbers wrong bar length family" }; var STALLARD_PHILOSOPHY = { lever: "stallard_philosophy", action: "Stallard SST/trailing micro: Speedway Motors EMi squaring guides + slick/average setup sheet \u2192 square rear \u2192 front LF 11 in / RF 9-1/2 in torsion-to-axle \xB7 built-in caster ~15\u201320\xB0 on spindle", reason: "Stallard public docs (Speedway EMi): Stallard-specific front arm lengths \u2014 not PMP 6 in upright reference" }; var STALLARD_FRONT = { lever: "stallard_front_geometry", action: "Front geometry (Stallard EMi guide): LF torsion bar center to axle center 11 in \xB7 RF 9-1/2 in \xB7 equal top/bottom rod moves when squaring \xB7 re-check LF after RF caster tweaks \xB7 toe ~1/16 in out", reason: "Stallard Micro Sprint Front Axle Squaring Guide (manufacturer): 11 in / 9.5 in are Stallard baseline references" }; var STALLARD_DOCS = { lever: "stallard_docs", action: "Stallard references: rear squaring guide \xB7 front squaring guide \xB7 spindle caster guide \xB7 slick/average setup sheet \xB7 SST assembly manual \u2014 use matching sheet for track state", reason: "Stallard public docs (Speedway EMi): manufacturer geometry before community stagger bands" }; var PACE_PHILOSOPHY = { lever: "pace_philosophy", action: "Pace/PMP 600 trailing micro: official PMP 600 setup sheet (SpeedMart/GRE6) is the published baseline \u2014 LR 12-1/2 in arm, PMP ride heights to bar center, PMP torsion bar chart", reason: "PMP 600 setup sheet (manufacturer): Pace/PMP family published numbers \u2014 strongest non-Hyper PA-adjacent sheet set" }; var PACE_PMP_SHEET = { lever: "pace_pmp_sheet", action: "PMP baseline (manufacturer sheet): rear square rod 10-3/16\u201310-1/4 in \xB7 axle-to-tube 12-3/16\u201312-1/4 in \xB7 front upright-to-axle 6\u20136-1/8 in \xB7 LR bar split turn chart (50 pt = 1-1/2 LR turns, etc.)", reason: "PMP 600cc Micro-Sprint Setup Sheet: manufacturer-published squaring + bar split procedure" }; var TENJ_PHILOSOPHY = { lever: "ten_j_philosophy", action: "Ten J (CA micro): private setup sheets via dealer \u2014 trailing-arm torsion arms + 10 in Keizer wheels \xB7 square and scale using Ten J sheet before Hyper or PMP numbers", reason: "Ten J Chassis (manufacturer): setup sheets are dealer/private \u2014 public site confirms trailing-arm platform not Hyper clone" }; var TENJ_COMMUNITY = { lever: "ten_j_community", action: "Ten J start (community): trailing-arm squaring family (12-1/2 in LR arm pattern) until Ten J sheet in hand \xB7 contact Ten J for banking-specific baseline \xB7 do not assume Hyper X6 bars", reason: "Community (600 micro trailing-arm): Ten J public detail thin \u2014 PMP squaring order applies until builder sheet received" }; var SAWYER_PHILOSOPHY = { lever: "sawyer_philosophy", action: "Sawyer 626: wishbone rear micro \u2014 use SawyerChassis.com FAQ setup sheet + rear axle spacing diagram \xB7 lightweight spindle front end \xB7 square before PMP trailing-arm numbers (wrong family)", reason: "Sawyer public site (manufacturer): 626 uses wishbones \u2014 Hyper trailing-arm/PMP upright refs do not apply directly" }; var SAWYER_SETUP = { lever: "sawyer_setup", action: "Sawyer 626 baseline: download FAQ setup sheet from SawyerChassis.com \xB7 log rear axle spacing per factory diagram \xB7 treat as wishbone micro \u2014 arm length sets bar size like Hyper philosophy, not Hyper hard numbers", reason: "Sawyer public docs (manufacturer): 626 setup sheet is the brand baseline \u2014 community wing/stagger bands secondary" }; var SAWYER_CAUTION = { lever: "sawyer_wishbone_caution", action: "Sawyer vs trailing-arm brands: 626 wishbone \u2014 use Sawyer sheet + Hyper directional principles only \xB7 never copy Stallard 11 in / 9.5 in front arms or PMP 6 in upright measure", reason: "Sawyer wishbone vs Stallard/PMP trailing-arm: front squaring references are not interchangeable" }; var FACTOR1_PHILOSOPHY = { lever: "factor1_philosophy", action: "Factor 1 micro: Chassis 101 fundamentals \u2014 square baseline \u2192 psi fundamentals \u2192 shock phase tuning \xB7 tight in = loose off (90% forward bite issues) \xB7 steering tuned ~20% more turn right than left", reason: "Factor 1 Racing Chassis 101 (manufacturer blog): philosophy and shock matrix \u2014 limited public geometry numbers" }; var FACTOR1_SHOCKS = { lever: "factor1_shocks", action: "Shock tuning (Factor 1 Chassis 101): tight in \u2192 LF compression up / RF compression adjust / LR rebound up \xB7 tight off \u2192 RF rebound up / LR compression up \xB7 loose in \u2192 opposite LF/RF compression \xB7 log valving with track state", reason: "Factor 1 Chassis 101 (manufacturer): published shock direction matrix for dirt micro/sprint" }; var FACTOR1_STEERING = { lever: "factor1_steering", action: "Steering (Factor 1 Chassis 101): aim for ~20% more steering angle to the right than left \xB7 incorrect steering setup masks scaling work \u2014 verify before cross/stagger chase", reason: "Factor 1 Chassis 101 (manufacturer): steering asymmetry is part of Factor 1 baseline prep" }; var FACTOR1_TIGHT_LOOSE = { lever: "factor1_tight_loose_off", action: "Tight in / loose off (Factor 1): entry fix likely caused exit loose \u2014 undo last entry tighten before exit loosen stack \xB7 address cross/stagger/psi on entry phase separately from exit", reason: "Factor 1 Chassis 101 (manufacturer): phase diagnosis before second lever on opposite end of track" }; var FACTOR1_COMMUNITY = { lever: "factor1_community", action: "Factor 1 geometry (community): trailing-arm micro family for squaring \u2014 use PMP/GRE6 squaring order and ride-height-to-bar method until Factor 1 sheet available \xB7 validate arm lengths differ from PMP", reason: "Community (600 micro): Factor 1 public geometry thin \u2014 trailing-arm community squaring is start band" }; var BUILDER_PRIORS2 = { d1: (track) => [ D1_PHILOSOPHY, D1_REAR_SQUARE, TRAILING_SQUARING_REAR, TRAILING_BIRDCAGE, TRAILING_SQUARING_FRONT, D1_VS_TRAILING, TRAILING_SCALING, ...trailingArmTrackCore(track) ], stallard: (track) => [ STALLARD_PHILOSOPHY, STALLARD_DOCS, stallardTrackPrior(track), TRAILING_SQUARING_REAR, TRAILING_BIRDCAGE, STALLARD_FRONT, TRAILING_SCALING, ...trailingArmTrackCore(track) ], pace: (track) => [ PACE_PHILOSOPHY, PACE_PMP_SHEET, TRAILING_SQUARING_REAR, TRAILING_BIRDCAGE, TRAILING_SQUARING_FRONT, TRAILING_SCALING, ...trailingArmTrackCore(track) ], ten_j: (track) => [ TENJ_PHILOSOPHY, TENJ_COMMUNITY, TRAILING_PHILOSOPHY, TRAILING_SQUARING_REAR, TRAILING_BIRDCAGE, TRAILING_SQUARING_FRONT, ...trailingArmTrackCore(track) ], sawyer: (track) => [ SAWYER_PHILOSOPHY, SAWYER_SETUP, SAWYER_CAUTION, ...sawyerTrackPriors(track), TRAILING_SCALING, trailingStaggerPrior(track), psiForTrailingTrack(track), wingForTrailingTrack(track), ...directionPriorsForTrack2(track), track === "drying" ? TRAILING_DRYING_TRANSITION : null ].filter(Boolean), factor1: (track) => [ FACTOR1_PHILOSOPHY, FACTOR1_TIGHT_LOOSE, FACTOR1_STEERING, factor1ShocksForTrack(track), FACTOR1_COMMUNITY, TRAILING_SQUARING_REAR, crossForTrailingTrack(track), rideHeightForTrack2(track), trailingStaggerPrior(track), psiForTrailingTrack(track), wingForTrailingTrack(track), ...directionPriorsForTrack2(track), track === "drying" ? TRAILING_DRYING_TRANSITION : null ].filter(Boolean) }; var PHILOSOPHY_LINES2 = { d1: { slick: "D1 slick/freeing: square per Driven PDF first \u2014 less rear stagger + lower cross before bar turns on trailing-arm (PMP directional ref)", default: D1_PHILOSOPHY.action }, stallard: { slick: "Stallard slick: switch to EMi slick/average setup sheet \u2014 LF 11 in / RF 9.5 in front refs \xB7 lower stagger/cross as track frees", wet: "Stallard wet/heavy: average sheet + taller blocks \xB7 conservative cross/stagger pairing before caster/toe chase", default: STALLARD_PHILOSOPHY.action }, pace: { slick: "Pace/PMP slick: PMP sheet directional \u2014 stagger to ~4\u20136 in, cross ~42\u201345%, wing back before RF shock stack", wet: "Pace/PMP wet: 2 in front / 2-1/4 rear blocks \xB7 stagger 7\u20139 in upper if loose \xB7 hold cross mid-band first laps", default: PACE_PHILOSOPHY.action }, ten_j: { slick: "Ten J slick: trailing-arm community start until dealer sheet \u2014 take stagger out before cross/wing add (PMP order)", default: TENJ_PHILOSOPHY.action }, sawyer: { slick: "Sawyer 626 slick: wishbone FAQ sheet \u2014 less stagger, wing back; never copy trailing-arm Stallard/PMP front measures", wet: "Sawyer 626 wet: confirm axle spacing diagram before cross experiments on wishbone geometry", default: SAWYER_PHILOSOPHY.action }, factor1: { slick: "Factor 1 slick: tight in = loose off \u2014 entry fix likely caused exit push; shock matrix + less stagger before cross add", default: FACTOR1_PHILOSOPHY.action } }; function isMicroNonHyperDeepBuilder(mfrKey) { return DEEP_BUILDERS2.has(mfrKey); } function microSprint600ChassisPriors(mfrKey, enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 16; const fn = BUILDER_PRIORS2[mfrKey]; if (!fn) return []; const track = normalizeTrack3(trackState); return withCaveat2(fn(track)).slice(0, max); } function pickMicroNonHyperPhilosophyLine(mfrKey, trackState) { const track = normalizeTrack3(trackState); const lines = PHILOSOPHY_LINES2[mfrKey]; if (!lines) return TRAILING_PHILOSOPHY.action; if ((track === "slick" || track === "drying") && lines.slick) return lines.slick; if (track === "wet" && lines.wet) return lines.wet; return lines.default; } // scripts/lib/experimental/thinDataChassisPhilosophy.mjs var THIN_DATA_CAVEAT = "Thin public data for this chassis \u2014 philosophy and logged A-B-A runs lead; no invented geometry or setup numbers here."; var GRASSROOTS_DEEP_QM = /* @__PURE__ */ new Set(["stanley", "bullrider", "nc"]); var GRASSROOTS_DEEP_LIGHTNING = /* @__PURE__ */ new Set(["hyper"]); var THIN_DATA_PRIORITY = { lever: "thin_data_priority", action: "Data policy: record scale sheet, psi/stagger/cross (or bar turns on sprint), and entry/mid/exit feel every run \u2014 when public geometry is thin, your log is the knowledge base", reason: "thin-data chassis policy: no published geometry for this builder \u2014 A-B-A history replaces generic advice fastest" }; var ADVANCED_THIN_DATA_PRIORITY = { lever: "thin_data_priority", action: "Data policy: log scale, LR/RR bar turns, wing, stagger, and phase feel every session \u2014 limited public geometry means your A-B-A log is the primary source", reason: "[DATA POLICY] Builder reference is frame only until your logged findings exist" }; var DIVISION_NOTES = { quarter_midget: { lever: "thin_data_qm", action: "QM thin-data (community): without a Stanley/Bullrider/NC sheet, log squaring reference points YOUR builder uses \xB7 psi + stagger + cross from scale \xB7 one change per run \xB7 dealer/sheet beats forum numbers", reason: "community (QM): Sherman and other QM builders have limited public geometry \u2014 builder baseline or your logged scale is primary" }, outlaw_kart: { lever: "thin_data_outlaw", action: "Outlaw thin-data (community): offset cage kart \u2014 cross %, seat cradle, wing, inch stagger primary \xB7 NOT flat-kart rear track-width math \xB7 log scale before copying another brand's sheet", reason: "community (outlaw kart): unknown or thin-brand outlaws need logged cross/stagger \u2014 public detail varies by builder" }, micro_sprint_600: { lever: "thin_data_micro600", action: "Micro 600 thin-data (community): without Hyper/D1/Stallard-class sheet, log blocks/torsion/panhard/wing turns \xB7 squaring before bar chase \xB7 do not copy full sprint or QM numbers", reason: "community (600 micro): non-Hyper builders often have private sheets \u2014 logged scale + builder shop card when available" }, lightning_sprint: { lever: "thin_data_lightning", action: 'Lightning thin-data (community): 13" wheel motorcycle-engine class \u2014 Hyper/Saldana sheets differ \xB7 log ride height to torsion bar, wing, and psi \xB7 NOT 600 micro or full midget geometry', reason: "community (Lightning sprint): Saldana and thin-brand Lightning setups rely on builder/dealer notes \u2014 generic class priors only scaffold" }, maxim_sprint_winged: { lever: "thin_data_sprint", action: "Sprint builder ref: without Mach 1/J&J/KRK/Fletcher shop card, log LR/RR bar turns, scale, and wing each session \xB7 square before bar stack \xB7 class stagger/RF bands are reference only \u2014 not your builder's index holes", reason: "[BUILDER REF] Beast/Gaerte/Maxim-name without deep module \u2014 shop card or your logged scale leads" }, hyper_midget: { lever: "thin_data_midget", action: "Midget builder ref: without advanced builder card, log panhard/J-bar height, LR platform, and scale \xB7 midget left-side and stagger differ from 410 \xB7 Eagle/Hyper class priors are reference frame only", reason: "[BUILDER REF] USAC/POWRi midget \u2014 builder card or A-B-A log leads; cross-brand copy is unreliable" } }; function hasDeepChassisKnowledge(profile2, mfrKey) { if (!profile2) return false; if (profile2 === "flat_kart") return true; if (!mfrKey) return false; if (isAdvancedChassisProfile(profile2)) return isAdvancedDeepBuilder(mfrKey); if (profile2 === "quarter_midget") return GRASSROOTS_DEEP_QM.has(mfrKey); if (profile2 === "outlaw_kart") return isOutlawDeepBuilder(mfrKey); if (profile2 === "micro_sprint_600") { return mfrKey === "hyper" || isMicroNonHyperDeepBuilder(mfrKey); } if (profile2 === "lightning_sprint") return GRASSROOTS_DEEP_LIGHTNING.has(mfrKey); return false; } function isThinDataChassisCase(profile2, enrichedVc = {}) { if (!profile2 || profile2 === "flat_kart") return false; const key = enrichedVc.chassis_mfr_key; const conf = enrichedVc.chassis_routing_confidence; if (conf === "missing" || !key) return true; if (conf === "unknown") return true; return !hasDeepChassisKnowledge(profile2, key); } function divisionThinPrior(profile2) { return DIVISION_NOTES[profile2] || null; } function withThinCaveat(profile2, priors) { const caveat = isAdvancedChassisProfile(profile2) ? ADVANCED_THIN_DATA_CAVEAT : THIN_DATA_CAVEAT; return priors.map((p) => ({ ...p, caveat })); } function thinDataChassisPriors(profile2, enrichedVc = {}, _trackState, opts = {}) { if (!isThinDataChassisCase(profile2, enrichedVc)) return []; const max = opts.maxPriors ?? 8; const mfr = enrichedVc.chassis_manufacturer || enrichedVc.chassis_name || "your builder"; const div = divisionThinPrior(profile2); const isAdvanced = isAdvancedChassisProfile(profile2); const priors = [ isAdvanced ? ADVANCED_THIN_DATA_PRIORITY : THIN_DATA_PRIORITY, DRIVER_BUILDER_MINDSET, DRIVER_BUILDER_SEAT_FEEDBACK, DRIVER_BUILDER_REPEATABILITY, DRIVER_BUILDER_SYSTEMS ]; if (div) priors.push(div); if (enrichedVc.chassis_routing_confidence === "missing") { priors.splice(1, 0, { lever: "thin_data_missing_chassis", action: "Add chassis manufacturer + model in Garage \u2192 Setup \u2192 Chassis \u2014 unlocks builder routing when public data exists", reason: `thin-data (${profile2}): chassis unknown \u2014 class baseline only until builder is on file` }); } else if (enrichedVc.chassis_mfr_key && enrichedVc.chassis_routing_confidence === "identified") { priors.push({ lever: "thin_data_identified_builder", action: `${mfr} thin-data: public geometry for this builder is limited \u2014 start from dealer/shop card if available \xB7 log scale + feel \xB7 do not copy another brand's holes or cross bands`, reason: `thin-data (${profile2} \xB7 ${mfr}): identified builder without deep public module \u2014 logged user data leads` }); } return withThinCaveat(profile2, priors).slice(0, max); } function augmentWithThinDataLayer(profile2, enrichedVc, trackState, priors = [], opts = {}) { if (!isThinDataChassisCase(profile2, enrichedVc)) return priors; const thin = thinDataChassisPriors(profile2, enrichedVc, trackState, opts); const used = new Set(priors.map((p) => p.lever)); const extra = thin.filter((p) => !used.has(p.lever)); const max = opts.maxPriors ?? 16; return [...extra, ...priors].slice(0, max); } function resolveThinDataDeepCaveat(profile2, enrichedVc = {}) { if (!isThinDataChassisCase(profile2, enrichedVc)) return null; return THIN_DATA_CAVEAT; } function pickThinDataChassisBrief(profile2, enrichedVc = {}) { if (!isThinDataChassisCase(profile2, enrichedVc)) return null; const div = divisionThinPrior(profile2); const mfr = enrichedVc.chassis_manufacturer || enrichedVc.chassis_name; const key = enrichedVc.chassis_mfr_key || enrichedVc.chassis_profile_key; if (mfr && key && !hasDeepChassisKnowledge(profile2, key)) { if (isAdvancedChassisProfile(profile2)) { return `${mfr}: limited public geometry \u2014 your scale + A-B-A log leads; industry principles apply until shop card is on file`; } return `${mfr}: thin public data \u2014 balance, seat feedback, one change per run \xB7 logged A-B-A leads`; } if (isAdvancedChassisProfile(profile2)) { return div ? `${div.action.split("\xB7")[0].trim()} \u2014 your log leads` : "Limited builder geometry on file \u2014 log scale, bar turns, and phase feel; your A-B-A history is the authority"; } return div ? `${div.action.split("\xB7")[0].trim()} \u2014 logged data leads` : "Thin chassis data \u2014 log scale + feel; your A-B-A history is the accuracy driver"; } // scripts/lib/experimental/advancedChassisRouting.mjs var ADVANCED_CHASSIS_PROFILES = /* @__PURE__ */ new Set([ "maxim_sprint_winged", "hyper_midget" ]); var ADVANCED_MFR_PATTERNS = { maxim_sprint_winged: [ { key: "mach1", re: /\bmach[\s-]?1\b|\bmach1\b|mach1chassis/, mfr: "Mach 1" }, { key: "jj", re: /\bj&j\b|\bjj\b|\bjesse[\s-]?jon/, mfr: "J&J" }, { key: "krk", re: /\bkrk\b|keen speed|keen racing/, mfr: "KRK Racing" }, { key: "fletcher", re: /fletcher/, mfr: "Fletcher Racing" }, { key: "maxim", re: /maxim/, mfr: "Maxim Racing" } ], hyper_midget: [ { key: "mach1", re: /\bmach[\s-]?1\b|\bmach1chassis/, mfr: "Mach 1" }, { key: "jj", re: /\bj&j\b|\bjj\b|\bjesse[\s-]?jon/, mfr: "J&J" }, { key: "krk", re: /\bkrk\b|keen speed/, mfr: "KRK Racing" }, { key: "fletcher", re: /fletcher/, mfr: "Fletcher Racing" }, { key: "hyper", re: /hyper|emmick|kiwi|spike|dmi|bell/, mfr: "Hyper Racing" }, { key: "eagle", re: /eagle|spike midget/, mfr: "Eagle / Spike Midget" } ] }; var DEEP_BUILDERS3 = /* @__PURE__ */ new Set(["mach1", "jj", "krk", "fletcher"]); function isAdvancedChassisProfile(profile2) { return ADVANCED_CHASSIS_PROFILES.has(profile2); } function isAdvancedDeepBuilder(mfrKey) { return DEEP_BUILDERS3.has(mfrKey); } function detectAdvancedChassisManufacturer(profile2, vc2 = {}) { if (!isAdvancedChassisProfile(profile2)) { return { key: null, mfr: null, confidence: "n/a" }; } const hay = [ vc2.chassis_name, vc2.chassis_mfr, vc2.chassis_manufacturer, vc2.car_name, vc2.trait_notes ].filter(Boolean).join(" ").toLowerCase(); if (!hay.trim()) { return { key: null, mfr: null, confidence: "missing" }; } const patterns = ADVANCED_MFR_PATTERNS[profile2] || []; for (const p of patterns) { if (p.re.test(hay)) { return { key: p.key, mfr: p.mfr, confidence: "identified" }; } } return { key: null, mfr: null, confidence: "unknown" }; } function enrichAdvancedVehicleContext(vc2 = {}, profile2 = "") { const mfr = detectAdvancedChassisManufacturer(profile2, vc2); return { ...vc2, chassis_manufacturer: mfr.mfr || vc2.chassis_mfr || vc2.chassis_manufacturer || null, chassis_mfr_key: mfr.key, chassis_routing_confidence: mfr.confidence, advanced_chassis_layer: isAdvancedChassisProfile(profile2) }; } function advancedChassisManufacturerPriors(profile2, enrichedVc = {}, trackState, opts = {}) { if (!isAdvancedChassisProfile(profile2)) return []; const key = enrichedVc.chassis_mfr_key; if (!key || !isAdvancedDeepBuilder(key)) return []; if (key === "mach1") return mach1ChassisPriors(enrichedVc, trackState, { ...opts, profile: profile2 }); if (key === "jj") return jjSprintChassisPriors(enrichedVc, trackState, opts); if (key === "krk") return krkChassisPriors(enrichedVc, trackState, opts); if (key === "fletcher") return fletcherSprintChassisPriors(enrichedVc, trackState, { ...opts, profile: profile2 }); return []; } function resolveAdvancedChassisDeepCaveat(profile2, enrichedVc = {}) { if (!isAdvancedChassisProfile(profile2)) return null; const key = enrichedVc.chassis_mfr_key; if (key === "mach1") return MACH1_CAVEAT; if (key === "jj") return JJ_CAVEAT; if (key === "krk") return KRK_CAVEAT; if (key === "fletcher") return FLETCHER_CAVEAT; return ADVANCED_CHASSIS_CAVEAT; } function pickAdvancedPhilosophyLine(mfrKey, trackState) { if (mfrKey === "mach1") return pickMach1PhilosophyLine(trackState); if (mfrKey === "jj") return pickJjPhilosophyLine(); if (mfrKey === "krk") return pickKrkPhilosophyLine(); if (mfrKey === "fletcher") return pickFletcherPhilosophyLine(trackState); return null; } function resolveAdvancedChassisHint(vc2 = {}, profile2 = "", trackState) { const enriched = enrichAdvancedVehicleContext(vc2, profile2); const line = pickAdvancedPhilosophyLine(enriched.chassis_mfr_key, trackState); if (line) return line; return pickThinDataChassisBrief(profile2, enriched); } // scripts/lib/experimental/advancedTrackSnap.mjs var TRACK_SNAP_TAG = "[TRACK SNAP]"; var USER_REPORTED_TAG = "[USER REPORTED]"; var SNAP_PRESETS = { slicking: { label: "Slicking up", trend: "slicking", track_state: "dry_slick" }, rubbering: { label: "Rubbering up", trend: "rubbering", track_state: "heavy" }, drying_lanes: { label: "Drying lanes", trend: "drying_lanes", track_state: "drying", laneHint: true }, cooling: { label: "Cooling off", trend: "cooling", track_state: "tacky" }, stable: { label: "Stable", trend: "stable", track_state: null } }; var TRACK_SNAP_STORAGE_KEY = "bb_track_snaps_v1"; var DEFAULT_SNAP_MAX_AGE_MS = 90 * 60 * 1e3; function normalizeTrackSnap(snap = {}) { const trend = snap.trend || snap.preset || "custom"; const preset = SNAP_PRESETS[trend] || null; return { ts: snap.ts || (/* @__PURE__ */ new Date()).toISOString(), session: String(snap.session || snap.phase || "Tonight").trim() || "Tonight", trend, label: snap.label || preset?.label || String(trend).replace(/_/g, " "), track_state: snap.track_state || preset?.track_state || null, note: snap.note ? String(snap.note).trim().slice(0, 160) : null, source: "track_snap" }; } function latestTrackSnap(trackSnaps = [], maxAgeMs = DEFAULT_SNAP_MAX_AGE_MS) { const now = Date.now(); for (const raw of trackSnaps) { const snap = normalizeTrackSnap(raw); const age = now - new Date(snap.ts).getTime(); if (!Number.isFinite(age) || age <= maxAgeMs) return snap; } return null; } function formatTrackSnapPriorAction(recentSnap, analysis = {}) { if (!recentSnap) return ""; const bits = [`You reported: ${recentSnap.label}`]; if (recentSnap.note) bits.push(recentSnap.note); if (analysis.trendSource === "track_snap") { bits.push("Snap is steering tonight's evolution read until more session log fills in"); } else if (analysis.loggedTrend) { bits.push("Session log still leads \u2014 snap is supplemental"); } return bits.join(" \xB7 "); } function trackSnapPlanningLine(recentSnap, analysis = {}) { if (!recentSnap) return null; const lead = analysis.trendSource === "track_snap" ? USER_REPORTED_TAG : TRACK_SNAP_TAG; const note = recentSnap.note ? ` \u2014 ${recentSnap.note}` : ""; return `${lead} ${recentSnap.session}: ${recentSnap.label}${note}`; } function buildUniversalTrackSnapPriors(trackCondition = {}, trackState) { const recent = latestTrackSnap(trackCondition.track_snaps || []); if (!recent) return []; const sessionLog = (trackCondition.session_log || []).filter((r) => r?.source !== "track_snap"); const analysis = { trendSource: sessionLog.length >= 3 ? "session_log" : "track_snap", loggedTrend: sessionLog.length >= 2 ? null : null }; return [{ lever: "track_snap_report", action: formatTrackSnapPriorAction(recent, analysis), reason: `${USER_REPORTED_TAG} Optional track snap \u2014 logged session data overrides when you have more runs` }]; } function pickTrackSnapBriefLine() { return null; } // scripts/lib/experimental/advancedTrackEvolution.mjs var STATE_RANK = { wet: 0, greasy: 1, tacky: 2, heavy: 3, dry_slick: 4, slick: 5 }; var SLICK_STATES = /* @__PURE__ */ new Set(["dry_slick", "slick", "heavy", "drying"]); function normalizeTrackState(raw) { const s = String(raw || "").toLowerCase().trim(); if (!s) return "unknown"; if (s === "dry" || s === "dry-slick") return "dry_slick"; if (s === "drying" || s === "drying_out") return "drying"; return s; } function stateRank(state) { return STATE_RANK[normalizeTrackState(state)] ?? null; } function sessionLabel(row) { return String(row?.session || row?.phase || "run").trim(); } function formatTrendChain(rows) { return rows.slice(-5).map((r) => { const bits = [sessionLabel(r)]; if (r.track_state) bits.push(`(${normalizeTrackState(r.track_state)})`); if (r.lane) bits.push(`\xB7 ${r.lane}`); return bits.join(" "); }).join(" \u2192 "); } function analyzeSessionTrend(sessionLog = [], currentState) { const log = Array.isArray(sessionLog) ? sessionLog.filter(Boolean) : []; const states = log.map((r) => normalizeTrackState(r.track_state)).filter((s) => s !== "unknown"); if (currentState && normalizeTrackState(currentState) !== "unknown") { states.push(normalizeTrackState(currentState)); } let trend = "stable"; let delta = 0; if (states.length >= 2) { const first = stateRank(states[0]); const last = stateRank(states[states.length - 1]); if (first != null && last != null) delta = last - first; if (delta >= 2) trend = "slicking"; else if (delta <= -2) trend = "cooling"; else if (delta === 1) trend = "slicking"; else if (delta === -1 && /heavy|rubber/.test(states.join(" "))) trend = "rubbering"; } const lanes = log.map((r) => String(r.lane || "").toLowerCase()).filter(Boolean); let laneTrend = "stable"; if (lanes.length >= 2) { const low = /bottom|low|inside|1/.test(lanes[0]); const high = /top|high|outside|3|4/.test(lanes[lanes.length - 1]); const midLate = /mid|middle|2/.test(lanes[lanes.length - 1]); if (low && (midLate || high)) laneTrend = "moving_up"; if (high && /low|bottom/.test(lanes[lanes.length - 1])) laneTrend = "moving_down"; } const notes = log.map((r) => String(r.note || "").toLowerCase()).join(" "); if (/delay|rain|cooled|cool off|temp drop/.test(notes)) trend = "cooling"; const line = log.length ? formatTrendChain(log) : null; return { trend, laneTrend, delta, sessionCount: log.length, sessionLogCount: log.filter((r) => r.source !== "track_snap").length, stateSequence: states, loggedTrend: line, firstState: states[0] || normalizeTrackState(currentState), lastState: states[states.length - 1] || normalizeTrackState(currentState) }; } function trackSnapsAsSessionRows(trackSnaps = []) { return trackSnaps.map((raw) => { const snap = normalizeTrackSnap(raw); const noteBits = [snap.label]; if (snap.note) noteBits.push(snap.note); noteBits.push("(track snap)"); return { ts: snap.ts, session: snap.session, track_state: snap.track_state, lane: snap.trend === "drying_lanes" ? "moving up" : null, note: noteBits.join(" \u2014 "), source: "track_snap", snap_trend: snap.trend }; }); } function mergeEvolutionTimeline(sessionLog = [], trackSnaps = []) { const sessions = (Array.isArray(sessionLog) ? sessionLog : []).map((row) => ({ ...row, source: row.source || "session_log" })); const snaps = trackSnapsAsSessionRows(trackSnaps); return [...sessions, ...snaps].filter(Boolean).sort((a, b) => new Date(a.ts || 0).getTime() - new Date(b.ts || 0).getTime()).slice(-16); } function resolveEvolutionTrend(sessionAnalysis, trackSnaps = [], opts = {}) { const recent = latestTrackSnap(trackSnaps, opts.maxSnapAgeMs ?? DEFAULT_SNAP_MAX_AGE_MS); const sessionOnlyCount = sessionAnalysis.sessionLogCount ?? sessionAnalysis.sessionCount ?? 0; let trend = sessionAnalysis.trend; let trendSource = sessionOnlyCount >= 2 ? "session_log" : "blended"; if (recent?.trend && recent.trend !== "stable") { const snapTrend = recent.trend === "drying_lanes" ? "slicking" : recent.trend; if (sessionOnlyCount >= 3) { trendSource = "session_log"; } else { trend = snapTrend; trendSource = "track_snap"; } } return { ...sessionAnalysis, trend, trendSource, recentSnap: recent, snapCount: Array.isArray(trackSnaps) ? trackSnaps.length : 0 }; } function buildEvolutionContext(trackCondition = {}, trackState) { const sessionLog = trackCondition.session_log || []; const trackSnaps = trackCondition.track_snaps || []; const sessionAnalysis = analyzeSessionTrend(sessionLog, trackState); const analysis = resolveEvolutionTrend(sessionAnalysis, trackSnaps); const prediction = predictTrackEvolution(analysis, trackCondition); return { sessionLog, trackSnaps, analysis, prediction, recentSnap: analysis.recentSnap }; } function predictTrackEvolution(analysis, trackContext = {}) { const cur = analysis.lastState || "unknown"; const rank = stateRank(cur); const line = trackContext.line_progression || trackContext.line || null; let likelyNext = cur; let confidence2 = "low"; if (analysis.trend === "slicking") { likelyNext = rank != null && rank < STATE_RANK.slick ? "dry_slick" : "slick"; confidence2 = analysis.delta >= 2 ? "moderate" : "low"; } else if (analysis.trend === "rubbering") { likelyNext = "heavy"; confidence2 = "moderate"; } else if (analysis.trend === "cooling") { likelyNext = "tacky"; confidence2 = "moderate"; } else if (cur === "tacky" && analysis.sessionCount >= 1) { likelyNext = "dry_slick"; confidence2 = "low"; } if (line && typeof line === "object") { if (line.late && /rubber|heavy|slick/.test(String(line.late).toLowerCase())) { likelyNext = normalizeTrackState(likelyNext); if (analysis.trend === "stable") confidence2 = "low"; } } const drying = trackContext.drying_prediction || trackContext.drying; const dryNote = typeof drying === "string" ? drying : drying?.note || drying?.summary || null; return { likelyNext, confidence: confidence2, dryingNote: dryNote }; } function classifyNightPhase(sessionCount, lastSessionName = "") { const name = String(lastSessionName).toLowerCase(); if (/feature|main|a-main|final/.test(name)) return "late"; if (/consi|b-main|lcq|last chance/.test(name)) return "late"; if (sessionCount >= 4 || /heat 2|heat 3|qual 2|dash/.test(name)) return "mid"; if (sessionCount <= 1) return "early"; return "mid"; } function wingedProfile(profile2) { return profile2 === "maxim_sprint_winged" || profile2 === "hyper_midget"; } function aggressivenessGuidance(phase, trend) { if (phase === "late") { return { lever: "advanced_evo_conservative_late", action: "Late session (consi/feature): plan the NEXT change before rolling out \u2014 if the trend holds, pre-stage stagger/wing/bar on the scale sheet but only commit ONE on track; protect tires over hero geometry", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Late-night moves need pre-thought \u2014 stacked levers erase the trend you logged") }; } if (phase === "early" && trend === "slicking") { return { lever: "advanced_evo_aggressive_early", action: "Early night + slicking trend: you can test one geometry lever per session IF scale + PIVOT baseline is locked \u2014 log hot RR psi each run so later sessions inherit a clean trend", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Early sessions are for building the night's trend log \u2014 not stacking three moves before heat 2") }; } return { lever: "advanced_evo_mid_conservative", action: "Mid-event: match change size to trend confidence \u2014 small wing/bar steps when slicking is obvious; hold platform if last run was within 0.2s and RH drift is zero", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Mid-night = confirm trend before doubling down") }; } function advancedTrackEvolutionPriors(enrichedVc = {}, trackState, trackCondition = {}, opts = {}) { const profile2 = opts.profile || trackCondition?.vehicle_context?.advanced_profile || "maxim_sprint_winged"; if (!isAdvancedChassisProfile(profile2)) return []; const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const max = opts.maxPriors ?? 5; const tc = trackCondition || {}; const sessionLog = tc.session_log || enrichedVc.session_log || []; const trackSnaps = tc.track_snaps || enrichedVc.track_snaps || []; const evoCtx = opts.evoCtx || buildEvolutionContext({ ...tc, session_log: sessionLog, track_snaps: trackSnaps }, trackState); const analysis = evoCtx.analysis; const prediction = evoCtx.prediction; const recentSnap = evoCtx.recentSnap; const lastSess = sessionLog.length ? sessionLog[sessionLog.length - 1] : null; const phase = classifyNightPhase(analysis.sessionLogCount ?? analysis.sessionCount, sessionLabel(lastSess)); const priors = []; const cur = normalizeTrackState(trackState); const isWinged = wingedProfile(profile2); const m = enrichedVc.setup_measurements || {}; if (analysis.trend === "slicking" || SLICK_STATES.has(cur) || SLICK_STATES.has(prediction.likelyNext)) { priors.push({ lever: "advanced_evo_slicking_geometry", action: "If track continues to slick up: LR bite via bar down OR stagger out 1/4 in BEFORE front platform hero \u2014 fix RH drift first; slick tracks punish stacked bar + wing on a rolled platform", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Slicking shifts lever order toward rear platform + tire management") }); if (isWinged) { priors.push({ lever: "advanced_evo_slicking_wing", action: "If grip keeps falling: wing down 0.5\xB0 per session max \u2014 entry push on slick often = excess wing on low RC; log bar height + wing together when either moves", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Wing and rear RC share the same balance triangle as the track frees") }); } } if (analysis.trend === "rubbering" || cur === "heavy" || prediction.likelyNext === "heavy") { priors.push({ lever: "advanced_evo_rubbering", action: "If rubber builds (heavy lane): protect RR temp \u2014 stagger may need back IN 1/8\u20131/4 in vs slick setup; wing add only after RR stop hopping, not before", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Rubber-up nights flip the slick-night stagger direction") }); } if (analysis.loggedTrend && analysis.sessionCount >= 2) { const wingNote = m.wing_angle != null ? ` \xB7 wing ${m.wing_angle}\xB0 on file` : ""; priors.push({ lever: "advanced_evo_session_trend", action: `Logged trend (${analysis.loggedTrend}): next session inherit ONE change from this chain \u2014 do not reset platform unless RH drifted${wingNote}`, reason: tagReason(ADVANCED_SOURCE.YOUR_DATA, "Multi-session log is the authority for what the track actually did") }); } if (prediction.likelyNext && prediction.likelyNext !== cur) { priors.push({ lever: "advanced_evo_forward_plan", action: `If track continues toward ${prediction.likelyNext.replace("_", " ")}: pre-plan bar OR wing OR stagger (pick one) before rolling out \u2014 current ${cur.replace("_", " ")} \u2192 likely ${prediction.likelyNext.replace("_", " ")} (${prediction.confidence} confidence)`, reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Forward state beats reacting only to the last lap") }); } else if (analysis.trend === "slicking" && (cur === "slick" || cur === "dry_slick")) { priors.push({ lever: "advanced_evo_forward_plan", action: "Track already slick \u2014 next sessions: tire/lane management over geometry hero moves \xB7 if RR temp spikes, stagger in before bar down \xB7 plan feature setup now, execute one lever per session", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Late slick phase is about protecting tires and lane, not adding bite") }); } if (analysis.laneTrend === "moving_up" || tc.line_progression) { priors.push({ lever: "advanced_evo_drying_lane", action: "Lane moving up as track dries: commit lane on scale sheet before bite chase \u2014 geometry can stay, but RR psi hot target and wing step depend on which lane you race", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Drying lane progression is a setup input separate from bar/wing") }); } if (analysis.trend === "cooling") { priors.push({ lever: "advanced_evo_cooling", action: "Track cooling / after delay: grip returns briefly \u2014 revert toward tacky baseline (psi down, wing up slightly) instead of keeping full slick-night setup; re-log RH before first hot lap", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Cool-off windows punish carrying slick setup into refreshed grip") }); } priors.push(aggressivenessGuidance(phase, analysis.trend)); return priors.filter((p) => !skip.has(p.lever)).slice(0, max); } function buildMultiSessionPlanning(trackCondition = {}, enrichedVc = {}, trackState, opts = {}) { const profile2 = opts.profile || trackCondition?.vehicle_context?.advanced_profile; if (profile2 && !isAdvancedChassisProfile(profile2)) { return { lines: [], trend: "unknown", sessionCount: 0 }; } const sessionLog = trackCondition.session_log || enrichedVc.session_log || []; const trackSnaps = trackCondition.track_snaps || enrichedVc.track_snaps || []; const evoCtx = opts.evoCtx || buildEvolutionContext({ ...trackCondition, session_log: sessionLog, track_snaps: trackSnaps }, trackState); const analysis = evoCtx.analysis; const prediction = evoCtx.prediction; const recentSnap = evoCtx.recentSnap; const lastSess = sessionLog.length ? sessionLog[sessionLog.length - 1] : null; const phase = classifyNightPhase(analysis.sessionLogCount ?? analysis.sessionCount, sessionLabel(lastSess)); const cur = normalizeTrackState(trackState); const lines = []; const snapLine = trackSnapPlanningLine(recentSnap, analysis); if (snapLine) lines.push(snapLine); if (analysis.trend === "slicking") { lines.push( `If the track continues to slick up: wing down 0.5\xB0 OR stagger out 1/4 in OR LR bar down \u2014 one lever per session, RH locked first` ); } else if (analysis.trend === "rubbering") { lines.push( "If rubber keeps building: stagger in 1/8\u20131/4 in vs slick setup \xB7 watch RR temp \xB7 wing add only after hop is gone" ); } else if (analysis.trend === "cooling") { lines.push( "If track keeps cooling: step back toward tacky baseline (psi down, slight wing up) \u2014 re-scale RH before hot laps" ); } else if (cur === "tacky" && analysis.sessionCount >= 1) { lines.push( "Typical progression from tacky: expect slicking through heats \u2014 log hot RR psi each session so feature setup inherits the trend" ); } if (analysis.loggedTrend && analysis.sessionCount >= 2) { lines.push(`Based on your logged trend (${analysis.loggedTrend}): inherit ONE planned change \u2014 do not reset platform mid-chain`); } if (prediction.likelyNext && prediction.likelyNext !== cur) { lines.push( `Likely next surface: ${prediction.likelyNext.replace("_", " ")} (${prediction.confidence} confidence from ${cur.replace("_", " ")} + session log)` ); } if (phase === "late") { lines.push("Feature/consi mindset: pre-plan the move, commit one lever, protect tires \u2014 no stacked geometry hero changes"); } const drying = prediction.dryingNote; if (drying) { lines.push(`Drying intel: ${drying}`); } return { trend: analysis.trend, laneTrend: analysis.laneTrend, likelyNext: prediction.likelyNext, confidence: prediction.confidence, sessionCount: analysis.sessionCount, nightPhase: phase, loggedTrend: analysis.loggedTrend, trendSource: analysis.trendSource, recentSnap, lines: lines.slice(0, 6) }; } function pickAdvancedTrackEvolutionBriefLine(trackCondition = {}, trackState, profile2) { if (!isAdvancedChassisProfile(profile2)) return null; const plan = buildMultiSessionPlanning(trackCondition, {}, trackState, { profile: profile2 }); if (!plan.lines.length) return null; if (plan.loggedTrend) { return `Evolution: ${plan.trend.replace("_", " ")} \xB7 ${plan.loggedTrend.split(" \u2192 ").slice(-2).join(" \u2192 ")}`; } return `Evolution: ${plan.trend.replace("_", " ")} \xB7 plan ${plan.likelyNext?.replace("_", " ") || "ahead"}`; } function isAdvancedTrackEvolutionProfile(profile2) { return isAdvancedChassisProfile(profile2); } // scripts/lib/experimental/crewChiefReport.mjs var T = { G1: { knob: "More siping", resp: "tire heat-up", good: 1, cond: "on cold/hard tires" }, G2: { knob: "Grooving the tire", resp: "lateral grip loss", good: -1, cond: "", strat: true, lore: "groove doesn't always help -- it flips with the surface" }, B1: { knob: "Engaging the bump stop", resp: "wheel hop", good: -1, cond: "on a rough/slick track", lore: "the bump can ADD hop, not just 'control' the car" }, B2: { knob: "Engaging the bump stop", resp: "bottoming", good: -1, cond: "" }, H1: { knob: "Softer helper springs", resp: "wheel hop", good: -1, cond: "on rough" }, P1: { knob: "Raising the panhard/J-bar", resp: "lateral grip loss", good: -1, cond: "", strat: true, lore: "'raise = bite' isn't universal -- it reverses by track state" }, P2: { knob: "Raising the panhard/J-bar", resp: "wheel hop", good: -1, cond: "on rough", lore: "higher bar can cost stability (more hop), not add it" }, S1: { knob: "Stiffer high-speed compression", resp: "wheel hop", good: -1, cond: "on rough", lore: "stiffer isn't more stable -- it can add hop" }, S2: { knob: "More compression", resp: "bottoming", good: -1, cond: "" }, S3: { knob: "More high-speed rebound", resp: "wheel hop", good: -1, cond: "on rough" }, S4: { knob: "More high-speed rebound", resp: "forward drive", good: 1, cond: "on rough", lore: "more rebound can HURT drive off, not help it" }, N1: { knob: "(null control)", resp: "lateral loss", good: -1, cond: "", nullc: true } }; var PLAIN_RESP = { "tire heat-up": "heat", "lateral grip loss": "grip loss", "wheel hop": "wheel hop", "bottoming": "bottoming", "forward drive": "drive off", "lateral loss": "lateral loss" }; var mag = (d) => { const a = Math.abs(d); return a >= 0.8 ? "big" : a >= 0.5 ? "solid" : a >= 0.2 ? "small" : "tiny"; }; function confidence(row) { if (row.verdict === "SIGNAL") return row.survives_bonferroni ? { level: "HIGH", action: "Trust it for similar track conditions. Put it in the book." } : { level: "MODERATE", action: "Real but not bulletproof (didn't survive multi-test correction). Re-run a clean A-B-A next similar night." }; if (row.verdict === "weak") return { level: "LOW", action: "A lean, not a finding. Needs more runs to confirm." }; if (row.verdict === "null/noise") return { level: "NONE", action: "No measurable effect here. Don't chase it." }; return { level: "INSUFFICIENT", action: "Not enough clean runs or variation. Get more A-B-A laps (aim 5-6 per setting)." }; } function translateRow(row) { const m = T[row.id] || { knob: row.id, resp: "the response", good: -1, cond: "" }; const c = confidence(row); if (m.nullc) { const fired = row.verdict === "SIGNAL"; return { id: row.id, stratum: row.stratum, level: fired ? "WARNING" : "OK", line: fired ? "NULL CONTROL FIRED -- the math found a signal in a fake label. Treat tonight's findings with caution." : "Null control clean -- the engine isn't inventing signal. Good.", action: fired ? "Recheck the data/labels before trusting other findings." : "" }; } if (c.level === "INSUFFICIENT" || row.d == null) { return { id: row.id, stratum: row.stratum, level: c.level, line: `${m.knob}: not enough data to call it yet.`, action: c.action }; } const up = row.d > 0; const beneficial = Math.sign(row.d) * m.good > 0; const rp = PLAIN_RESP[m.resp] || m.resp; const moved = up ? `more ${rp}` : `less ${rp}`; const verb = c.level === "NONE" ? "showed no clear change in" : beneficial ? "helped" : "hurt"; const cond = m.cond ? " " + m.cond : ""; let line; if (c.level === "NONE") line = `${m.knob}${cond}: no clear change in ${rp}.`; else line = `${m.knob}${cond} -> ${moved}. That ${beneficial ? "HELPED" : "HURT"} (${mag(row.d)} effect` + (row.stratum ? `, ${row.stratum}` : "") + `).`; if (m.lore && c.level !== "NONE" && (c.level === "HIGH" || c.level === "MODERATE")) line += ` Data over myth: ${m.lore}.`; return { id: row.id, stratum: row.stratum, level: c.level, line, action: c.action }; } function reversalNote(rows, id) { const rs = rows.filter((r) => r.id === id && r.verdict === "SIGNAL" && r.d != null); if (rs.some((r) => r.d > 0) && rs.some((r) => r.d < 0)) return `${T[id]?.knob || id}: effect REVERSES by track state -- set it by the surface, not a fixed number.`; return null; } var pearson = (xs, ys) => { const n = xs.length; if (n < 6) return null; const mx = xs.reduce((a, b) => a + b, 0) / n, my = ys.reduce((a, b) => a + b, 0) / n; let c = 0, vx = 0, vy = 0; for (let i = 0; i < n; i++) { c += (xs[i] - mx) * (ys[i] - my); vx += (xs[i] - mx) ** 2; vy += (ys[i] - my) ** 2; } return vx && vy ? c / Math.sqrt(vx * vy) : null; }; var TREAT = { stagger: "stagger", panhard_height: "panhard height", comp_low: "low-speed compression", comp_high: "high-speed compression", reb_high: "high-speed rebound", bump_engaged: "bump stop", helper_soft: "helper spring", groove_density: "grooving", sipe_count: "siping" }; var RESP = { wheel_hop_energy: "wheel hop", bottoming_rate: "bottoming", lateral_loss: "lateral loss", forward_drive_index: "drive off", heat_up_rate: "heat-up", degradation_slope: "fall-off" }; function pairsCorr(corpus, group, names, thr) { const keys = Object.keys(names); const out = []; for (let i = 0; i < keys.length; i++) for (let j = i + 1; j < keys.length; j++) { const ka = keys[i], kb = keys[j]; const xs = [], ys = []; for (const r2 of corpus) { const a = group(r2)[ka], b = group(r2)[kb]; if (typeof a === "number" && isFinite(a) && typeof b === "number" && isFinite(b)) { xs.push(a); ys.push(b); } } if (new Set(xs).size < 2 || new Set(ys).size < 2) continue; const r = pearson(xs, ys); if (r != null && Math.abs(r) >= thr) out.push({ a: names[ka], b: names[kb], r: +r.toFixed(2), n: xs.length }); } return out; } function crewChiefReport(corpus, opts = {}) { const studies = opts.studies || STUDIES; const res = opts.result && opts.result.rows ? opts.result : runStudies(corpus, studies); const rows = res.rows; const translations = rows.map(translateRow); const rank = { HIGH: 0, MODERATE: 1, LOW: 2, NONE: 3, INSUFFICIENT: 4, WARNING: -1, OK: 9 }; const findings = translations.filter((t) => ["HIGH", "MODERATE"].includes(t.level)).map((t) => ({ t, d: Math.abs((rows.find((r) => r.id === t.id && r.stratum === t.stratum) || {}).d || 0) })).sort((a, b) => rank[a.t.level] - rank[b.t.level] || b.d - a.d).map((x) => x.t); const reversals = [...new Set(rows.filter((r) => T[r.id] && T[r.id].strat).map((r) => r.id))].map((id) => reversalNote(rows, id)).filter(Boolean); const nullRow = translations.find((t) => t.id === "N1"); const needMore = [...new Set(translations.filter((t) => t.level === "INSUFFICIENT").map((t) => t.id))]; const treatCorr = pairsCorr(corpus, (r) => r.setup || {}, TREAT, 0.6); const respCorr = pairsCorr(corpus, (r) => r.responses || {}, RESP, 0.6); const report = { header: Object.assign( { name: "Run Block", track: null, date: null, source: "real", n_runs: corpus.length }, opts.session || {} ), track_condition: opts.trackContext ? normalizeTrackContext(opts.trackContext) : null, key_findings: findings.slice(0, 8).map((t) => ({ study: t.id, stratum: t.stratum || null, level: t.level, text: t.line })), reversals, null_check: nullRow ? { status: nullRow.level === "WARNING" ? "WARNING" : "OK", text: nullRow.line } : null, needs_more_data: needMore, correlations: { treatments: treatCorr.map((c) => ({ a: c.a, b: c.b, r: c.r, n: c.n, note: `${c.a} and ${c.b} moved together (r=${c.r}) -- effects can't be separated; vary ONE at a time.` })), responses: respCorr.map((c) => ({ a: c.a, b: c.b, r: c.r, n: c.n, note: `${c.a} and ${c.b} tracked together (r=${c.r}) -- likely the same underlying issue.` })) }, studies: translations.filter((t) => t.id !== "N1").map((t) => { const row = rows.find((r) => r.id === t.id && r.stratum === t.stratum) || {}; return { study: t.id, stratum: t.stratum || null, level: t.level, text: t.line, action: t.action || "", d: row.d ?? null, z: row.z ?? null, verdict: row.verdict || null }; }), stats: { tests: res.m, z_threshold: res.z_adj, n_signal: res.n_signal, n_signal_bonf: res.n_signal_bonf }, generated_at: (/* @__PURE__ */ new Date()).toISOString() }; return { report, res, translations, findings, reversals, nullRow, needMore, treatCorr, respCorr }; } function windCompass(deg) { if (deg == null || !isFinite(deg)) return "?"; const dirs = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]; return dirs[Math.round((deg % 360 + 360) % 360 / 22.5) % 16]; } function formatTrackConditionSummary(tc, opts = {}) { if (!tc) return { lines: ["(no track condition context attached for this block)"], sections: [] }; const sections = [], lines = [], g = (k) => tc[k]; const dir = g("track_direction"), dirConf = g("track_direction_confidence"); const orient = g("orientation_preset") || g("orientation"); const wind = g("wind_context"); const dry = g("drying_prediction"); const line = g("line_progression") || g("line"); const sess = g("session_log"); const snaps = g("track_snaps"); const dryingSec = g("drying_sections"); let dirLine = ""; if (dir && dir !== "unknown") dirLine = `Direction: ${String(dir).toUpperCase()}${dirConf != null ? " (" + Math.round(dirConf * 100) + "%)" : ""}`; else if (g("direction_detail") && g("direction_detail").straight_heading_deg != null) dirLine = `Heading ~${Math.round(g("direction_detail").straight_heading_deg)}\xB0 (lap direction inconclusive)`; if (orient && orient.front_straight_deg != null) { const o = `Orientation: front ${Math.round(orient.front_straight_deg)}\xB0 / back ${Math.round(orient.back_straight_deg || (orient.front_straight_deg + 180) % 360)}\xB0 ${(orient.direction || "CCW").toUpperCase()}`; dirLine = dirLine ? dirLine + " \xB7 " + o : o; } if (dirLine) { lines.push(dirLine); sections.push({ key: "direction", label: "Direction & orientation", text: dirLine }); } const wFrom = wind && wind.wind_from_deg != null ? wind.wind_from_deg : dry && dry.wind_from_deg; const wSpd = wind && wind.wind_speed_mph != null ? wind.wind_speed_mph : g("wind_mph"); if (wind && wind.summary || wFrom != null) { let wLine = wind && wind.summary ? wind.summary : ""; if (wFrom != null) wLine += (wLine ? " \xB7 " : "") + `Wind ${windCompass(wFrom)} (${Math.round(wFrom)}\xB0)` + (wSpd != null ? " @ " + wSpd + " mph" : ""); lines.push(wLine); sections.push({ key: "wind", label: "Wind", text: wLine }); } if (dry) { const note = typeof dry === "string" ? dry : dry.note || dry.summary || null; if (note) { const conf = dry.confidence ? ` [${dry.confidence}]` : ""; const dLine = "Drying: " + note + conf; lines.push(dLine); sections.push({ key: "drying", label: "Drying prediction", text: note, meta: { confidence: dry.confidence, fastest: dry.fastest_section, slowest: dry.slowest_section } }); } } if (dryingSec && dryingSec.summary) { lines.push("Sections: " + dryingSec.summary); sections.push({ key: "sections", label: "Section drying model", text: dryingSec.summary }); } if (line) { if (typeof line === "string") { lines.push("Line: " + line); sections.push({ key: "line", label: "Line progression", text: line }); } else { const parts = []; if (line.early) parts.push("Early: " + line.early); if (line.mid) parts.push("Mid: " + line.mid); if (line.late) parts.push("Late: " + line.late); if (parts.length) { parts.forEach((p) => lines.push(p)); sections.push({ key: "line", label: "Line progression", text: parts.join(" | "), phases: { early: line.early || null, mid: line.mid || null, late: line.late || null } }); } } } if (Array.isArray(snaps) && snaps.length) { const recent = latestTrackSnap(snaps); if (recent) { const snapLine = `[TRACK SNAP] ${recent.session}: ${recent.label}${recent.note ? " \u2014 " + String(recent.note).slice(0, 60) : ""}`; lines.push(snapLine); sections.push({ key: "track_snap", label: "Track snap", text: snapLine, meta: { trend: recent.trend, ts: recent.ts } }); } } if (Array.isArray(sess) && sess.length) { const recent = sess.slice(0, 5).map((r) => { const bits = [r.session || "run"]; if (r.track_state) bits.push(r.track_state); if (r.lane) bits.push(r.lane); if (r.note) bits.push(String(r.note).slice(0, 60)); return bits.join(" \xB7 "); }); lines.push("Session log: " + recent.join(" || ")); sections.push({ key: "session_log", label: "Between-run log", items: recent }); } const hasEvoInput = Array.isArray(sess) && sess.length >= 2 || Array.isArray(snaps) && snaps.length; if (hasEvoInput && opts.advancedProfile) { const plan = buildMultiSessionPlanning(tc, opts.vehicleContext || {}, tc.track_state, { profile: opts.advancedProfile }); if (plan.lines && plan.lines.length) { lines.push("Plan ahead: " + plan.lines[0]); sections.push({ key: "track_evolution", label: "Track evolution \xB7 plan ahead", items: plan.lines, meta: { trend: plan.trend, likelyNext: plan.likelyNext, nightPhase: plan.nightPhase, trendSource: plan.trendSource } }); } } const intel = []; if (g("photo_count")) intel.push(g("photo_count") + " photo" + (g("photo_count") === 1 ? "" : "s")); if (g("track_walk_context") && g("track_walk_context").walk_points) intel.push(g("track_walk_context").walk_points + " walk pts"); const ic = g("instrumentation_context"); if (ic) { if (ic.lidar_count) intel.push(ic.lidar_count + " LiDAR"); if (ic.thermal_count) intel.push(ic.thermal_count + " thermal"); } if (intel.length) { lines.push("Intel: " + intel.join(" \xB7 ")); sections.push({ key: "intel", label: "Instrumentation", text: intel.join(" \xB7 ") }); } if (g("event_context")) { const ec = g("event_context"); const prof = ec.profile ? `${ec.profile.length_mi || "?"} mi \xB7 ~${ec.profile.lap_target_s || "?"}s laps` : null; const evLine = `${ec.event || "Event"} mode${prof ? " \xB7 " + prof : ""}${ec.mode ? " (" + ec.mode + ")" : ""}`; lines.unshift(evLine); sections.unshift({ key: "event", label: "Event mode", text: evLine, meta: ec }); } if (g("track_state") || g("state")) { const st = "State: " + (g("track_state") || g("state")); lines.unshift(st); sections.unshift({ key: "state", label: "Track state", text: g("track_state") || g("state") }); } if (!lines.length) lines.push("Track context attached (fields: " + Object.keys(tc).join(", ") + ")"); return { lines, sections }; } function normalizeTrackContext(tc) { const fmt = formatTrackConditionSummary(tc); return Object.assign({}, tc, { summary_lines: fmt.lines, summary_sections: fmt.sections }); } function trackSummaryLines(tc) { if (!tc) return ["(no track condition context attached for this block)"]; if (tc.summary_lines && tc.summary_lines.length) return tc.summary_lines; return formatTrackConditionSummary(tc).lines; } function renderCrewChief(bundle, title) { const r = bundle.report || bundle; const L2 = []; const P = (s) => L2.push(s); P("=" + "=".repeat(60)); P(`CREW CHIEF REPORT -- ${title || r.header.name}`); const hh = [r.header.track, r.header.date, r.header.source, `${r.header.n_runs} runs`].filter(Boolean).join(" | "); if (hh) P(hh); P("=" + "=".repeat(60)); P(""); P("KEY FINDINGS"); if (!r.key_findings.length) P(" (no confident findings yet -- need more clean A-B-A runs)"); r.key_findings.slice(0, 5).forEach((f, i) => P(` ${i + 1}. [${f.level}] ${f.text}`)); r.reversals.forEach((x) => P(` * ${x}`)); if (r.null_check) P(` - Sanity: ${r.null_check.text}`); if (r.needs_more_data.length) P(` - Needs more data: ${r.needs_more_data.join(", ")}`); P(""); P("TRACK CONDITION SUMMARY"); trackSummaryLines(r.track_condition).forEach((x) => P(" " + x)); P(""); P("CORRELATION NOTES"); if (!r.correlations.treatments.length && !r.correlations.responses.length) P(" (nothing notable)"); r.correlations.treatments.forEach((c) => P(" ! " + c.note)); r.correlations.responses.forEach((c) => P(" ~ " + c.note)); P(""); P("EVERY STUDY (plain English)"); for (const s of r.studies) { P(` ${s.study}${s.stratum ? ":" + s.stratum : ""} [${s.level}]`); P(` ${s.text}`); if (s.action) P(` -> ${s.action}`); } P(""); P(`Stats: ${r.stats.tests} tests, family-wise z>=${r.stats.z_threshold} (Bonferroni). "HIGH" survived that bar.`); return L2.join("\n"); } function renderMarkdown(bundle) { const r = bundle.report || bundle; const L2 = []; const P = (s) => L2.push(s); P(`# Crew Chief Report -- ${r.header.name}`); const hh = [r.header.track, r.header.date, r.header.source, `${r.header.n_runs} runs`].filter(Boolean).join(" \xB7 "); if (hh) P(`*${hh}*`); P(""); P("## Key Findings"); if (!r.key_findings.length) P("_No confident findings yet -- need more clean A-B-A runs._"); r.key_findings.slice(0, 5).forEach((f, i) => P(`${i + 1}. **[${f.level}]** ${f.text}`)); r.reversals.forEach((x) => P(`- ${x}`)); if (r.null_check) P(`- _Sanity:_ ${r.null_check.text}`); if (r.needs_more_data.length) P(`- _Needs more data:_ ${r.needs_more_data.join(", ")}`); P(""); P("## Track Condition Summary"); trackSummaryLines(r.track_condition).forEach((x) => P("- " + x)); P(""); P("## Correlation / Confound Notes"); if (!r.correlations.treatments.length && !r.correlations.responses.length) P("_Nothing notable._"); r.correlations.treatments.forEach((c) => P(`- :warning: ${c.note}`)); r.correlations.responses.forEach((c) => P(`- ${c.note}`)); P(""); P("## Every Study"); P("| Study | Confidence | Takeaway | Do |"); P("|---|---|---|---|"); for (const s of r.studies) P(`| ${s.study}${s.stratum ? ":" + s.stratum : ""} | ${s.level} | ${s.text.replace(/\|/g, "/")} | ${(s.action || "").replace(/\|/g, "/")} |`); P(""); P(`*Stats: ${r.stats.tests} tests, Bonferroni z>=${r.stats.z_threshold}. HIGH = survived correction.*`); return L2.join("\n"); } function reportJSON(bundle) { return JSON.stringify(bundle.report || bundle, null, 2); } // scripts/lib/experimental/experimentIngest.mjs var num = (v) => v === "" || v == null ? null : typeof v === "number" ? v : isNaN(+v) ? v : +v; var round2 = (x, n) => { if (x == null || !isFinite(x)) return null; const p = 10 ** n; const r = Math.round(x * p) / p; return r === 0 ? 0 : r; }; var SETUP_FIELDS = [ "stagger", "panhard_height", "comp_low", "comp_high", "reb_low", "reb_high", "bump_engaged", "helper_soft", "groove_density", "sipe_count", "noise_label" ]; var RESP_FIELDS = [ "heat_up_rate", "wheel_hop_energy", "lateral_loss", "bottoming_rate", "forward_drive_index", "degradation_slope" ]; var INSTR_FIELDS = [ "tire_temp_pre_f", "tire_temp_post_f", "run_minutes", "shock_travel_used_in", "shock_travel_avail_in", "shock_bottom_events" ]; function deriveInstrumentation(row) { const out = {}; let hu = num(row.heat_up_rate); if (hu == null) { const pre = num(row.tire_temp_pre_f), post = num(row.tire_temp_post_f), mins = num(row.run_minutes); if (pre != null && post != null && mins != null && mins > 0) hu = round2((post - pre) / mins, 3); } out.heat_up_rate = hu; let br = num(row.bottoming_rate); if (br == null) { const ev = num(row.shock_bottom_events), laps = num(row.laps); const used = num(row.shock_travel_used_in), avail = num(row.shock_travel_avail_in); if (ev != null && laps != null && laps > 0) br = round2(ev / laps, 3); else if (used != null && avail != null && avail > 0) br = round2(Math.min(1.5, used / avail), 3); } out.bottoming_rate = br; return out; } function rowToRecord(row) { const setup = {}; for (const f of SETUP_FIELDS) { const v = f === "noise_label" ? row[f] || null : num(row[f]); setup[f] = v; } const responses = {}; for (const f of RESP_FIELDS) responses[f] = num(row[f]); const derived = deriveInstrumentation(row); if (responses.heat_up_rate == null) responses.heat_up_rate = derived.heat_up_rate; if (responses.bottoming_rate == null) responses.bottoming_rate = derived.bottoming_rate; return { source: "real", track_state: row.track_state || null, surface_temp_f: num(row.surface_temp_f), laps: num(row.laps), setup, responses, _provenance: { run_id: row.run_id || null, experiment: row.experiment || null, arm: row.arm || null, study_targets: row.study_targets || null, drk_filename: row.drk_filename || null, instrumentation: Object.fromEntries(INSTR_FIELDS.map((k) => [k, num(row[k])])), note: "real experiment run; logger responses come from fromDrk(.drk), pyrometer/shock derived here" } }; } function rowsToRecords(rows) { return rows.map(rowToRecord); } // scripts/lib/experimental/maximSprintBaselinePriors.mjs function normalizeSurfaceState(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var MAXIM_PUBLIC_BASELINE_CAVEAT = "Public baseline from Maxim Racing manufacturer data \u2014 validate with your own logged runs."; var MAXIM_WINGED_SPRINT_BASELINE = { source: "Maxim Racing 305/360 winged setup sheet (public)", rr_tire_in: "105", stagger_rear_in: { tacky: "12-13", drying: "11-12", slick: "10-11", greasy: "13-14", unknown: "12-13" }, ride_height_in: "floor to torsion bar center, no driver (adjust per track)", rf_arm_length_in: "13-1/2", fuel_assumption_gal: "15", wheel_offset: { tacky: "wider RR spacing vs slick \u2014 log birdcage offset on Maxim sheet", slick: "RR in vs tacky as grip falls off \u2014 one offset change per run", drying: "transition offsets toward slick spacing as sheen goes away" }, wing: { tacky: "lower top wing band vs slick \u2014 add only if loose entry", slick: "more top wing vs tacky OR step down for feature push \u2014 log each session", drying: "plan wing step with track evolution" }, driver_weight: { light: "light driver \u2014 may need taller rear block height vs sheet baseline", heavy: "heavy driver \u2014 may need shorter rear block height vs sheet baseline" } }; var M = MAXIM_WINGED_SPRINT_BASELINE; function staggerBand(bucket) { return M.stagger_rear_in[bucket] || M.stagger_rear_in.unknown; } var PRIORS_BY_BUCKET = { tacky: [ { lever: "stagger", action: `Log rear stagger near ${M.stagger_rear_in.tacky} in starting band (305/360 winged) \u2014 remeasure hot after laps 8-10`, reason: "public baseline (Maxim): sheet baseline ~13 in stagger with 105 in RR tire \u2014 adjust per track and driver" }, { lever: "ride_height", action: `Establish ride height (${M.ride_height_in}) before shocks or bars \u2014 assumes ~${M.fuel_assumption_gal} gal fuel`, reason: "public baseline (Maxim): manufacturer baseline \u2014 light/heavy driver may need rear block height change vs sheet" }, { lever: "rf_arm", action: `RF arm length baseline ~${M.rf_arm_length_in} in \u2014 verify square before wheel offset moves`, reason: "public baseline (Maxim): arm length + block height set front geometry on ladder-frame sprint" }, { lever: "wheel_offset", action: `Tacky: ${M.wheel_offset.tacky}`, reason: "public baseline (Maxim): wheel offset recommendations change with track grip on published sheet" }, { lever: "wing", action: `Tacky wing: ${M.wing.tacky} \u2014 1\xB0 at a time`, reason: "public baseline (Maxim): separate tacky vs slick wing guidance on manufacturer sheet" }, { lever: "tire", action: `Confirm RR tire roll-out (~${M.rr_tire_in} in class norm) before stagger math \u2014 log cold and hot`, reason: "public baseline (Maxim): stagger assumes consistent RR circumference \u2014 remeasure when tire or prep changes" }, { lever: "geometry", action: "Square axles and verify bearing carrier timing before booking setup (Maxim tech)", reason: "public baseline (Maxim): baseline setup expects slight per-track tweaks \u2014 geometry first" } ], drying: [ { lever: "stagger", action: `As sheen goes away, take stagger toward ${M.stagger_rear_in.drying} in before shock chasing`, reason: "public baseline (Maxim): slick transition usually wants less rear stagger on winged 305/360" }, { lever: "wheel_offset", action: M.wheel_offset.drying, reason: "public baseline (Maxim): offsets move with grip \u2014 match offset change to track state" }, { lever: "wing", action: M.wing.drying, reason: "public baseline (Maxim): wing angle often steps with track evolution on winged sprint programs" }, { lever: "rr", action: "Ease RR bite / LR stop before adding stagger on a freeing track", reason: "public baseline (Maxim): protect RR for drive as track slicks \u2014 one lever per run" }, { lever: "ride_height", action: "If entry push while drying: small rear block / ride-height tweak before wing add", reason: `public baseline (Maxim): ${M.driver_weight.light} \xB7 ${M.driver_weight.heavy}` } ], slick: [ { lever: "stagger", action: `Slick: reduce rear stagger toward ${M.stagger_rear_in.slick} in (1/4 in steps from tacky baseline)`, reason: "public baseline (Maxim): less stagger tightens \u2014 do not stack with wing and offset same heat" }, { lever: "wing", action: M.wing.slick, reason: "public baseline (Maxim): tacky vs slick wing baselines differ on manufacturer sheet" }, { lever: "wheel_offset", action: M.wheel_offset.slick, reason: "public baseline (Maxim): RR in can tighten \u2014 verify RF arm and squareness first" }, { lever: "comp", action: "Soften HS compression before adding rebound if hop appears on slick", reason: "public baseline (Maxim): stiff platform on slick often adds hop \u2014 comp before reb" }, { lever: "weight", action: "To tighten mid/exit on small tracks: LR + RF weight before large shock changes", reason: "public baseline (Maxim): cross toward LR-RF can tighten through the middle" }, { lever: "discipline", action: "One lever per run \u2014 block heights interact with fuel load and driver weight", reason: `public baseline (Maxim): sheet assumes ~${M.fuel_assumption_gal} gal fuel; rescale rear blocks for driver weight before feature` } ], greasy: [ { lever: "stagger", action: `Heavy/greasy: rear stagger toward ${M.stagger_rear_in.greasy} in upper band \u2014 1/4 in steps`, reason: "public baseline (Maxim): wet tracks often want more stagger before shock work" }, { lever: "wheel_offset", action: "Greasy: wider RR spacing vs slick \u2014 return toward tacky offsets as grip builds", reason: "public baseline (Maxim): offset band shifts with moisture \u2014 log each session" }, { lever: "wing", action: "Heavy track: start lower top wing vs slick baseline \u2014 add wing only if loose entry", reason: "public baseline (Maxim): mechanical grip is high \u2014 wing stacks quickly on heavy clay" }, { lever: "ride_height", action: "Verify rear block heights \u2014 wet may use taller rear blocks vs slick baseline", reason: `public baseline (Maxim): ${M.driver_weight.light} \xB7 ${M.driver_weight.heavy}` } ], unknown: [ { lever: "track_state", action: "Log tacky \u2192 drying \u2192 slick each session \u2014 Maxim baselines are condition-specific", reason: "public baseline (Maxim): manufacturer sheet separates tacky vs slick starting points" }, { lever: "stagger", action: `Rear stagger starting band ${staggerBand("unknown")} in with ~${M.rr_tire_in} in RR \u2014 remeasure hot`, reason: "public baseline (Maxim): ~13 in stagger tacky baseline on sheet \u2014 validate on your track" }, { lever: "ride_height", action: `Baseline ride height: ${M.ride_height_in} \u2014 ~${M.fuel_assumption_gal} gal fuel assumption`, reason: "public baseline (Maxim): adjust rear block height for light vs heavy driver vs sheet" }, { lever: "rf_arm", action: `RF arm ~${M.rf_arm_length_in} in starting point \u2014 square axles before changes`, reason: "public baseline (Maxim): arm length and offset interact \u2014 geometry first" }, { lever: "discipline", action: "One DOF per run \u2014 confirm with A-B-A before the feature", reason: "public baseline (Maxim): slight adjustments expected per track and driver" } ] }; function maximSprintWingedBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState(trackState); const list = PRIORS_BY_BUCKET[bucket] || PRIORS_BY_BUCKET.unknown; return list.filter((p) => !skip.has(p.lever)); } // scripts/lib/experimental/stanleyRsrQuarterMidgetKnowledge.mjs var STANLEY_RSR_QM_CAVEAT = "Public baseline from Stanley/RSR documentation and community knowledge \u2014 validate with your own data and specific chassis setup."; var PHILOSOPHY5 = { lever: "stanley_philosophy", action: "Stanley/RSR order: panhard holes \u2192 alignment bars \u2192 square rear (5 in) \u2192 square front (13.75 in RF) \u2192 caster/toe \u2192 narrow width \u2192 psi \u2192 ride height \u2192 scale (57% left/rear, 54% cross X-method) \u2014 write it down so you can reset", reason: "Stanley/RSR public guide: repeatable baseline sheet beats stacking heat-to-heat guesses" }; var PANHARD = { lever: "stanley_panhard", action: "Panhard start (Stanley/RSR): frame side 3rd hole down front and rear \xB7 rear birdcage bottom hole \xB7 front axle top hole \u2014 RF panhard sets RF axle center 13.75 in from inside of RF tube", reason: "Stanley/RSR public baseline: panhard hole positions are part of the brand baseline, not Bullrider/NC equivalents" }; var SQUARING_REAR = { lever: "stanley_squaring_rear", action: "Square rear (Stanley/RSR): back of rear axle to back of rear cross tube = 5 in \u2014 measure wide left/right; turn top AND bottom radius rods equally to keep birdcage timing", reason: "Stanley/RSR public baseline: rear square is the anchor \u2014 birdcage timing loss masks real setup learning" }; var SQUARING_FRONT = { lever: "stanley_squaring_front", action: "Square front (Stanley/RSR): in alignment bars with tie-rod jam nuts loose; after rear is square, match front-to-rear axle centerline bar-to-bar both sides", reason: "Stanley/RSR public baseline: front placement follows squared rear \u2014 not Maxim/QM generic squaring numbers" }; var BIRDCAGE_CASTER = { lever: "stanley_birdcage_caster", action: "Birdcage + caster (Stanley/RSR): LR carrier flush with inside main rail \xB7 time LR cage square to panhard bracket, RR square to caliper mount \xB7 LF spindle vertical = 0 deg caster \xB7 adjust RF radius rods equal/opposite \xB7 toe 0, pitman arms at 11 and 1 o'clock", reason: "Stanley/RSR public baseline: carrier timing and caster split are built into the axle \u2014 verify wheelbase after caster moves" }; var RIDE_HEIGHT = { lever: "stanley_ride_height", action: "Ride height (Stanley/RSR): 1.5 in without driver at frame reference \u2014 at front/rear cross tubes add 0.75 in (1.5 in RH = 2.25 in at cross tube); never mix driver-in vs driver-out on the same sheet", reason: "Stanley/RSR public baseline: cross-tube mark is the handler reference for reset baseline" }; var PSI = { lever: "stanley_psi", action: "Starting psi (Stanley/RSR public): RF 11 / RR 11 / LF 5 / LR 5 psi cold \u2014 log hot after laps 8-10 before the next 1 psi step", reason: "Stanley/RSR public baseline: right-side higher than left is normal on these cars \u2014 not sprint/midget psi bands" }; var SCALING5 = { lever: "stanley_scaling", action: "Scale (Stanley/RSR, no driver): ~57% left, ~57% rear, ~54% cross via X-method \u2014 ballast moves left/rear only; shock collars change cross ONLY; recheck ride height after cross before trusting numbers", reason: "Stanley/RSR public baseline: X-method = same move LR+RF, opposite RR+LF \u2014 ride heights stay flat while cross changes" }; var TRACK_WIDTH = { lever: "stanley_track_width", action: "Track width + wheels (Stanley/RSR): run narrow \u2014 RF hub no front spacer \xB7 LR tire ~1/8 in from radius rods \xB7 RR ~1/4 in from spring \xB7 3 in backspace left wheels, 4 in backspace right", reason: "Stanley/RSR public baseline: width and wheel backspace are baseline items before cross experiments" }; var TRACK_TIGHT = { lever: "stanley_track_tight", action: "Too tight / won't turn (Stanley/RSR public list): more stagger \xB7 raise rear panhard frame side \xB7 lower front panhard frame side \xB7 move front axle left \xB7 heavier RR spring \xB7 softer LR spring \u2014 one item per run", reason: "Stanley/RSR public at-track guidance \u2014 try stagger and panhard before shock chasing" }; var TRACK_LOOSE = { lever: "stanley_track_loose", action: "Loose (Stanley/RSR public list): less stagger \xB7 lower rear panhard frame side \xB7 softer RR spring \xB7 stiffer LR spring \xB7 stiffer RF spring \xB7 raise nose \u2014 one item per run", reason: "Stanley/RSR public at-track guidance \u2014 confirm with hot psi log before stacking spring and cross" }; var TRACK_BIKING = { lever: "stanley_track_biking", action: "Biking (Stanley/RSR public list): lower left side \xB7 move RS tires out \xB7 lower cross \xB7 raise front and rear panhard \xB7 add left-side percentage \u2014 address cross/width before big shock moves", reason: "Stanley/RSR public baseline: biking often cross/width related on narrow QM setups" }; var DIRECTION_TIGHT = { lever: "stanley_direction_tight", action: "Quick tighten (Stanley/RSR): X-method cross up ~0.5% OR ease RF/RR psi 1 psi \u2014 recheck ride height if cross moved; at track use stagger/panhard list above", reason: "Stanley/RSR public baseline: cross and RS psi before camber or random shock clicks" }; var DIRECTION_LOOSE = { lever: "stanley_direction_loose", action: "Quick loosen (Stanley/RSR): X-method cross down ~0.5% OR add RF/RR psi 1 psi \u2014 left/rear % needs ballast, not shock collar tricks", reason: "Stanley/RSR public baseline: loose/free often cross down or RS support \u2014 A-B-A on same track state" }; var PHASE_TUNING = { lever: "stanley_phase_tuning", action: "Phase tuning (Stanley/RSR QM): LF+RR affects entry \xB7 RF+LR affects exit \xB7 rear stagger primarily middle \xB7 push = too much rear bite \xB7 loose = too much front bite \u2014 diagnose phase before stacking levers", reason: "Stanley/RSR public baseline: entry vs exit vs middle use different corners on offset panhard QM" }; function normalizeTrack4(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (["tacky", "rubbered", "drying"].includes(s)) return "tacky"; return "unknown"; } function withCaveat3(priors) { return priors.map((p) => ({ ...p, caveat: STANLEY_RSR_QM_CAVEAT, reason: p.reason.replace(/^Stanley\/RSR public baseline:/, "Stanley/RSR doc:") })); } function stanleyRsrChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 12; const track = normalizeTrack4(trackState); const priors = [ PHILOSOPHY5, PANHARD, SQUARING_REAR, SQUARING_FRONT, BIRDCAGE_CASTER, RIDE_HEIGHT, PSI, SCALING5, TRACK_WIDTH, track === "slick" || track === "wet" ? TRACK_LOOSE : TRACK_TIGHT, PHASE_TUNING, track === "slick" ? DIRECTION_TIGHT : DIRECTION_LOOSE, TRACK_BIKING ]; if (enrichedVc.chassis_model === "RSR" || /rsr/i.test(String(enrichedVc.chassis_name || ""))) { priors[0] = { ...priors[0], action: `${priors[0].action} (RSR \u2014 use RSR alignment-bar squaring sequence from Ultimate QM sheet)` }; } return withCaveat3(priors).slice(0, max); } function pickStanleyRsrPhilosophyLine() { return PHILOSOPHY5.action; } // scripts/lib/experimental/bullriderQuarterMidgetKnowledge.mjs var BULLRIDER_QM_CAVEAT = "Public baseline from manufacturer documentation and community knowledge \u2014 validate with your own data and specific chassis setup."; function normalizeTrack5(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (["tacky", "rubbered", "drying"].includes(s)) return "tacky"; return "normal"; } function withCaveat4(priors) { return priors.map((p) => ({ ...p, caveat: BULLRIDER_QM_CAVEAT })); } var PHILOSOPHY6 = { lever: "bullrider_philosophy", action: "Bullrider order: level table \u2192 2 in rear / 1-1/4 in front blocks \u2192 panhard ~2 in from rail \u2192 time LR/RR birdcages (mark rods) \u2192 square rear \u2192 front axle + wheelbase \u2192 caster \u2192 camber \u2192 toe \u2192 tires \u2192 ride height \u2192 scale \u2014 write every number so you can reset", reason: "Bullrider doc: full squaring sequence before cross or stagger \u2014 Bullrider measurement points are not Stanley 5 in / 13.75 in RF" }; var SQUARING_BLOCKS = { lever: "bullrider_squaring_blocks", action: "Squaring prep (Bullrider): level surface \xB7 remove tires and shocks \xB7 2 in blocks under rear axle \xB7 1-1/4 in blocks under front axle \xB7 corner blocks under frame", reason: "Bullrider doc: block heights are the Bullrider reference plane \u2014 do not use Stanley 1.5/7/8 in or NC 4-3/4 in rear square on a Bullrider" }; var PANHARD_LR = { lever: "bullrider_panhard_lr", action: "Rear axle lateral (Bullrider step 4): square against outside of LR birdcage \xB7 spin panhard until bracket sits ~2 in from inside frame rail \xB7 then time birdcages before any ride-height work", reason: "Bullrider doc: 2 in panhard-to-rail is the Bullrider LR placement reference \u2014 not Stanley panhard hole stack" }; var BIRDCAGE_LR = { lever: "bullrider_birdcage_lr", action: "LR birdcage timing (Bullrider step 5): square behind LR cage \xB7 measure 2 in back from top heim \xB7 spin bottom radius rod until bottom heim is 2 in back \u2014 carrier vertical (90\xB0) \xB7 mark top and bottom rods", reason: "Bullrider doc: marked rods let you turn top and bottom equally when squaring the rear axle" }; var BIRDCAGE_RR = { lever: "bullrider_birdcage_rr", action: "RR birdcage timing (Bullrider step 6): mirror LR process on RR cage \u2014 square behind carrier \xB7 2 in reference on heims \xB7 adjust RR radius rods until carrier is square \xB7 mark rods before rear-axle squaring", reason: "Bullrider doc: RR timing before rear square \u2014 losing cage timing hides real setup learning" }; var SQUARING_REAR2 = { lever: "bullrider_squaring_rear", action: "Square rear (Bullrider step 7): with cages timed and panhard set, measure rear axle square left/right at the widest point on the axle \xB7 adjust top AND bottom radius rods equally using rod marks \xB7 re-check LR/RR timing after moves", reason: "Bullrider doc: equal top/bottom turns preserve birdcage timing \u2014 1/16 in axle error reads large on QM tracks" }; var SQUARING_FRONT2 = { lever: "bullrider_squaring_front", action: "Front axle + wheelbase (Bullrider steps 8\u201311): set front axle per Bullrider wheelbase procedure after rear is square \xB7 match right-side wheelbase \xB7 match left-side wheelbase to right \xB7 adjust LF lower control arm/strut to equalize bar-to-bar measurements", reason: "Bullrider doc: front placement follows squared rear \u2014 any wheelbase change forces caster/camber/toe re-check" }; var CASTER_CAMBER_TOE = { lever: "bullrider_caster_camber", action: "Caster/camber/toe (Bullrider steps 9\u201313): LF spindle vertical = 0\xB0 caster \xB7 roll RF with top/bottom radius rods equal and opposite so wheelbase stays fixed \xB7 set camber \xB7 set toe last \xB7 pitman/steering arms ~11 and 1 o'clock \xB7 re-square toe after hits", reason: "Bullrider doc + offset QM geometry: caster split is built into the axle \u2014 verify wheelbase after every caster move" }; var RIDE_HEIGHT2 = { lever: "bullrider_ride_height", action: "Ride height (Bullrider): after squaring, set LF/LR ~3/4\u20131-1/2 in and RF/RR ~1-1/2 in without driver (same rule every week) \xB7 measure at the same frame point Bullrider uses on your sheet \xB7 recheck after scaling", reason: "Bullrider doc: ride height follows mechanical square \u2014 mixed driver-in/out breaks comparisons" }; var PSI2 = { lever: "bullrider_psi", action: "Starting psi (Bullrider QM band): RF 10\u201311 \xB7 RR 10\u201311 \xB7 LF 5 \xB7 LR 5 psi cold \xB7 log hot after laps 8\u201310 before the next 1 psi step \xB7 right-side higher than left is normal on offset QM", reason: "QM offset baseline: Bullrider uses the same right-heavy cold split as most QM sheets \u2014 not sprint/micro psi bands" }; var SCALING6 = { lever: "bullrider_scaling", action: "Scale (Bullrider start): ~55\u201357% left \xB7 ~55\u201357% rear \xB7 ~52\u201355% cross without driver \xB7 ballast moves left/rear only \xB7 shock collars or X-method (LR+RF in, RR+LF out) for cross \xB7 recheck ride height after cross before trusting numbers", reason: "QM offset scaling: X-method changes cross without ride-height drift \u2014 Bullrider squaring references stay Bullrider-specific" }; var TRACK_WIDTH2 = { lever: "bullrider_track_width", action: "Track width (Bullrider): run LF/LR as close to frame as rules allow without rub \xB7 RR is the primary width lever \u2014 closer to frame adds side bite and tightens \xB7 log width before cross experiments", reason: "QM offset width: RR in/out moves effective cross and rear bite together on panhard QM cars" }; function staggerPrior2(track) { if (track === "slick" || track === "wet") { return { lever: "bullrider_stagger", action: "Stagger slick/freeing (Bullrider QM): unlocked rear 2\u20132-1/2 in \xB7 locked LR 2-1/2\u20133 in on high bank \u2014 take rear stagger out before cross chase \xB7 less banking = less rear stagger needed", reason: "QM locked-rear physics: stagger primarily drives mid-corner on offset QM \u2014 banking reduces required circumference split" }; } if (track === "tacky") { return { lever: "bullrider_stagger", action: "Stagger tacky (Bullrider QM): unlocked 2\u20132-1/2 in rear \xB7 locked LR 2-1/2\u20133-1/2 in \xB7 front ~1\u20132 in effective (circumference) \xB7 one 1/8 in rear step per run", reason: "QM stagger: rear split fixes middle rotation on locked LR \u2014 front stagger mostly jacks cross weight" }; } return { lever: "bullrider_stagger", action: "Stagger normal (Bullrider QM): unlocked 2\u20132-1/2 in rear \xB7 locked LR 2-1/2\u20133-1/2 in flat bullrings \xB7 front ~2 in target when tires allow \xB7 flat/tight tracks may need upper locked band \u2014 do not stack max stagger + max cross same heat", reason: "QM stagger: small flat tracks need more rear split; long straights punish over-stagger when LR is locked" }; } var TRACK_TIGHT2 = { lever: "bullrider_track_tight", action: "Too tight / won't turn (Bullrider QM): more rear stagger \xB7 raise rear panhard frame side \xB7 lower front panhard frame side \xB7 move front axle left \xB7 RR in toward frame \xB7 RF/RR psi down 1 \u2014 one item per run", reason: "QM panhard + stagger: entry push on offset QM \u2014 stagger and panhard height before shock chasing" }; var TRACK_LOOSE2 = { lever: "bullrider_track_loose", action: "Loose (Bullrider QM): less rear stagger \xB7 lower rear panhard frame side \xB7 RR out slightly \xB7 softer RR spring or RR turns out \xB7 cross down ~0.5% X-method \xB7 RF spring stiffer if RF overheating \u2014 one item per run", reason: "QM panhard + cross: loose center/exit on offset QM \u2014 confirm hot psi before stacking spring and cross" }; var TRACK_BIKING2 = { lever: "bullrider_track_biking", action: "Biking (Bullrider QM): lower left ride height \xB7 move RS tires out \xB7 cross down \xB7 raise front and rear panhard frame side \xB7 add left-side % with ballast \u2014 fix cross/width before big shock moves", reason: "QM offset: biking is usually cross/width on narrow LF/LR QM setups" }; var DIRECTION_TIGHT2 = { lever: "bullrider_direction_tight", action: "Quick tighten (Bullrider): X-method cross up ~0.5% OR RF/RR psi down 1 \xB7 recheck ride height if cross moved \xB7 at track use stagger/panhard list above \u2014 not Stanley 5 in rear square numbers", reason: "QM direction: cross and RS psi first on Bullrider \u2014 camber and random shock clicks last" }; var DIRECTION_LOOSE2 = { lever: "bullrider_direction_loose", action: "Quick loosen (Bullrider): X-method cross down ~0.5% OR RF/RR psi up 1 \xB7 rear stagger out 1/8 in on next run if mid still tight \xB7 left/rear % needs ballast, not collar tricks", reason: "QM direction: loose/free on offset QM \u2014 A-B-A on same track state beats stacking levers" }; var PHASE_TUNING2 = { lever: "bullrider_phase_tuning", action: "Phase tuning (Bullrider QM): LF+RR affects entry \xB7 RF+LR affects exit \xB7 rear stagger primarily middle \xB7 push = too much rear bite \xB7 loose = too much front bite \u2014 diagnose phase before stacking levers", reason: "QM handling physics: entry vs exit vs middle use different corners on offset panhard cars" }; function bullriderQuarterMidgetChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 14; const track = normalizeTrack5(trackState); const priors = [ PHILOSOPHY6, SQUARING_BLOCKS, PANHARD_LR, BIRDCAGE_LR, BIRDCAGE_RR, SQUARING_REAR2, SQUARING_FRONT2, CASTER_CAMBER_TOE, RIDE_HEIGHT2, PSI2, SCALING6, TRACK_WIDTH2, staggerPrior2(track), track === "slick" || track === "wet" ? TRACK_LOOSE2 : TRACK_TIGHT2, track === "slick" ? DIRECTION_TIGHT2 : DIRECTION_LOOSE2, TRACK_BIKING2, PHASE_TUNING2 ]; const size = String(enrichedVc.chassis_model || enrichedVc.chassis_name || "").match(/\b(76|78|80)\b/); if (size) { priors[0] = { ...priors[0], action: `${priors[0].action} (${size[1]}" wheelbase Bullrider \u2014 verify wheelbase steps on your size sheet)` }; } return withCaveat4(priors).slice(0, max); } function pickBullriderPhilosophyLine() { return PHILOSOPHY6.action; } // scripts/lib/experimental/ncQuarterMidgetKnowledge.mjs var NC_QM_CAVEAT = "Public baseline from manufacturer documentation and community knowledge \u2014 validate with your own data and specific chassis setup."; function normalizeTrack6(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (["tacky", "rubbered", "drying"].includes(s)) return "tacky"; return "normal"; } function withCaveat5(priors) { return priors.map((p) => ({ ...p, caveat: NC_QM_CAVEAT })); } function wheelbaseInches(enrichedVc) { const raw = `${enrichedVc.chassis_model || ""} ${enrichedVc.chassis_name || ""}`; const m = raw.match(/\b(76|78|80)\b/); if (!m) return null; const map = { 76: "46", 78: "48", 80: "50" }; return { size: m[1], inches: map[m[1]] }; } var PHILOSOPHY7 = { lever: "nc_philosophy", action: "NC Chassis order: level car \xB7 square rear 4-3/4 in to rear bar center \xB7 1/2\u20133/4 in front lead (never square) \xB7 panhard top holes \xB7 left-side finger ride height once \xB7 RF/RR shock turns from set \xB7 scale ~53.3% cross \xB7 stagger by class \u2014 serial number unlocks NC rod-length master sheet", reason: "NC Nevro/Coggin setup: mechanical NC references first \u2014 not Stanley 5 in rear or Bullrider 2 in panhard offset" }; var SQUARING_REAR3 = { lever: "nc_squaring_rear", action: "Square rear (NC): measure rear side of axle to center of rear bar = 4-3/4 in both sides \xB7 carriers 90\xB0 to ground (square against carrier back and floor) \xB7 adjust LR/RR radius rods equally \xB7 1/16 in error reads huge on QM", reason: "NC setup guide: 4-3/4 in rear square is the NC anchor \u2014 Stanley uses 5 in to rear cross tube, Bullrider uses block + panhard method" }; var FRONT_LEAD = { lever: "nc_front_lead", action: "Front lead (NC): run 1/2\u20133/4 in lead always \u2014 never square front axle \xB7 check after every hit \xB7 LF shock mount parallel to ground with no shims \xB7 LF axle as far right as possible without hitting upright", reason: "NC setup guide: front lead is a fixed NC geometry item \u2014 square front axle is wrong on N/C cars" }; var WHEELBASE = { lever: "nc_wheelbase", action: 'Wheelbase (NC): tape from back of front shock mount plate to center of rear carrier \u2014 46 in (76" car) \xB7 48 in (78") \xB7 50 in (80") \xB7 always run front axle this way after rear square', reason: "NC setup guide: wheelbase is size-specific on NC frames \u2014 re-check caster/toe after any change" }; var PANHARD2 = { lever: "nc_panhard", action: "Panhard (NC): rear \u2014 top hole frame side, bottom hole on carrier \xB7 front \u2014 top hole frame side \xB7 slide rear axle right until LR shock nearly touches LR top radius rod \xB7 re-verify 4-3/4 in rear square after lateral move", reason: "NC setup guide: panhard height sets roll center \u2014 lower rear frame hole adds RR bite and heat; lower front with it if you must drop rear" }; var RR_WIDTH = { lever: "nc_rr_width", action: "RR track width (NC): run RR as close to frame as rules allow \u2014 closer RR = more side bite and tighter car \xB7 RF run tight to shock without rub \xB7 LF/LR as close to frame as possible", reason: "NC setup guide: RR lateral position is a primary NC tighten lever before cross chasing" }; var RIDE_HEIGHT3 = { lever: "nc_ride_height", action: 'Ride height (NC left-side method): set left side once \u2014 LF until 2nd knuckle fits under frame rail behind tire \xB7 LR until fingers fit to knuckle line \xB7 RF/RR from coil "set" (shock fully extended, zero preload) then RF +2 turns \xB7 LR +2 turns from set at baseline', reason: "NC setup guide: left side stays fixed unless LF/LR hit track \u2014 right side turns tune grip per corner" }; var PSI3 = { lever: "nc_psi", action: "Hot psi targets (NC tire chart): RR 15 \xB7 RF 15 \xB7 LF 10 \xB7 LR 4\u20135 psi HOT \xB7 circumferences RR 34.5\u201336 in \xB7 LR/LF 31\u201332.5 in \xB7 RF 32\u201334 in \xB7 log cold and hot every session", reason: "NC setup guide: NC runs higher hot RS psi than many QM sheets \u2014 match circumference before inch stagger math" }; var SCALING7 = { lever: "nc_scaling", action: "Scale (NC): match LF/RF corner weights first \xB7 cross (LR+RF)/total \u2248 53.3% target \xB7 left side 53\u201355% band \xB7 use equal shock turns around the car (LR+RF in, RR+LF out) to move cross without ride-height drift", reason: "NC setup guide: NC cross target runs slightly lower than Stanley 54% \u2014 X-method still applies on coil cars" }; var STEERING = { lever: "nc_steering", action: "Steering (NC): spindle jig \u2014 pitman arms 11 and 1 o'clock \xB7 LF 1 turn toe-out (~1/8 in) after jig \xB7 re-check after any front hit \u2014 toe knocks out easily on QM", reason: "NC setup guide: steering geometry is part of NC baseline, not a race-night afterthought" }; var SPRING_BASELINE = { lever: "nc_spring_baseline", action: "Shock turns from set (NC race baseline): RF +2 turns from set at start \xB7 RR +2 turns at start \xB7 after car stable, bleed RR to ~1 turn out from set \xB7 LR spring 10 lb stiffer than RR (reverse split) \xB7 >2 turns in/out means wrong spring rate", reason: "NC setup guide: coil turns move tire temps \u2014 RF cools when stiffened, RR heats when stiffened" }; function staggerPrior3(track) { if (track === "slick") { return { lever: "nc_stagger", action: "Stagger slick (NC): locked LR 2-1/2\u20133 in \xB7 unlocked 2\u20132-1/2 in \xB7 high bank may run unlocked 2-1/2 in \xB7 front ~1\u20132 in \u2014 take rear out before cross down chase", reason: "NC setup guide: banking reduces required rear stagger \u2014 slick frees with less circumference split" }; } if (track === "wet") { return { lever: "nc_stagger", action: "Stagger heavy/greasy (NC): locked LR up to 3-1/2 in on flat tight tracks \xB7 unlocked 2\u20132-1/2 in \xB7 front ~2 in when tires allow \u2014 avoid max stagger + max cross same heat", reason: "NC setup guide: flat small tracks need upper locked band; moisture stacks grip quickly on QM" }; } return { lever: "nc_stagger", action: "Stagger (NC by class): unlocked Honda/Stock/Mod 2\u20132-1/2 in rear \xB7 locked Light B / AA 2-1/2\u20135 in (run 2-1/2\u20133-1/2 in normal) \xB7 Heavy B stay unlocked ~2-1/2 in \xB7 front target ~2 in (1 in compromise on modern tires)", reason: "NC setup guide: locked LR classes need rear stagger for mid-corner \u2014 front stagger mostly jacks cross" }; } var TRACK_PUSH = { lever: "nc_track_push", action: "Push / tight (NC): confirm RF +2 turns from set \xB7 verify left-side finger heights \xB7 take turns OUT of RR to free center (RR hooked up causes push) \xB7 harder RR compound if temps allow \xB7 cross down ~0.5% if RF overloaded", reason: "NC setup guide: push is often RR bite, not LF lack of grip \u2014 RR turns out before panhard drop" }; var TRACK_LOOSE3 = { lever: "nc_track_loose", action: "Loose (NC): add turns to RR spring \xB7 move RR in toward carrier \xB7 lower rear panhard to 2nd frame hole (lower front panhard to match if needed) \xB7 cross up slightly \xB7 add rear stagger 1/8 in \u2014 one lever per run", reason: "NC setup guide: loose rear \u2014 stiffer RR and RR in before max cross; watch RR temp (hot = tighter)" }; var TEMP_TUNING = { lever: "nc_temp_tuning", action: "Tire temps (NC): target RF \u2248 RR \u2248 LR hot \xB7 RF hot \u2192 stiffen RF (turns in) \xB7 RF cold \u2192 soften RF (turns out) \xB7 LR cold vs RR \u2192 stiffen RF to hold weight on LR \xB7 LR must stay as hot as RR for balance", reason: "NC setup guide: equal three-tire temps (RF/RR/LR) is the NC night goal \u2014 turns before random psi swings" }; var DIRECTION_TIGHT3 = { lever: "nc_direction_tight", action: "Quick tighten (NC): cross down ~0.5% OR RF/RR hot psi down 1 cold step \xB7 RR out 1/8 in effective on next run if mid push \xB7 do not copy Bullrider 2 in panhard or Stanley 13.75 in RF on NC", reason: "NC direction: NC cross runs ~53% \u2014 tighten with cross/RR width before copying other brands' geometry" }; var DIRECTION_LOOSE3 = { lever: "nc_direction_loose", action: "Quick loosen (NC): cross up ~0.5% OR RR turns in 1/2 \xB7 RR in toward frame \xB7 rear stagger out 1/8 in \u2014 left % moves with ballast only", reason: "NC direction: loose exit on NC \u2014 RR bite and cross before LF camber chase" }; var ALIGNMENT_KIT = { lever: "nc_alignment_kit", action: "NC alignment kit: one-person rear axle square, front wheelbase, lead, caster rotation, spindle alignment \u2014 use with G7/G9/G17 rod-length master sheet matched to your serial (brake master bracket stamp)", reason: "NC Chassis product docs: kit removes guesswork on tube center \u2014 pair with Master Set-Up Sheet rod lengths" }; var PHASE_TUNING3 = { lever: "nc_phase_tuning", action: "Phase tuning (NC QM): LF+RR entry \xB7 RF+LR exit \xB7 rear stagger middle \xB7 RR width moves cross and bite together \u2014 diagnose phase before stacking panhard + stagger + cross", reason: "QM handling physics on NC offset: same phase rules as other panhard QM cars" }; function ncQuarterMidgetChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 14; const track = normalizeTrack6(trackState); const wb = wheelbaseInches(enrichedVc); const priors = [ PHILOSOPHY7, SQUARING_REAR3, FRONT_LEAD, wb ? { ...WHEELBASE, action: WHEELBASE.action.replace(/46 in \(76" car\)[^·]+· 50 in \(80"\)/, `${wb.inches} in (${wb.size}" NC car)`) } : WHEELBASE, PANHARD2, RR_WIDTH, RIDE_HEIGHT3, PSI3, SCALING7, STEERING, SPRING_BASELINE, staggerPrior3(track), track === "slick" ? TRACK_PUSH : TRACK_LOOSE3, track === "slick" ? DIRECTION_TIGHT3 : DIRECTION_LOOSE3, TEMP_TUNING, ALIGNMENT_KIT, PHASE_TUNING3 ]; return withCaveat5(priors).slice(0, max); } function pickNcPhilosophyLine() { return PHILOSOPHY7.action; } // scripts/lib/experimental/hyperRacingGrassrootsKnowledge.mjs var HYPER_RACING_CAVEAT = "Public baseline from Hyper Racing documentation \u2014 validate with your own data and specific chassis setup."; function normalizeTrack7(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; return "normal"; } function isWingless(vc2 = {}) { const c = String(vc2.car_class || vc2.class_name || "").toLowerCase(); if (vc2.winged === false) return true; if (/non.?wing|wingless|no.?wing/.test(c)) return true; return false; } function isZLink(vc2 = {}) { const text = `${vc2.chassis_name || ""} ${vc2.chassis_model || ""}`.toLowerCase(); return /z-link|z link|zlink/.test(text); } function withCaveat6(priors) { return priors.map((p) => ({ ...p, caveat: HYPER_RACING_CAVEAT })); } var MICRO_PHILOSOPHY = { lever: "hyper_micro_philosophy", action: "Hyper 600 micro philosophy: squaring kit first (axle square, offsets, chain) \u2192 blocks/bars/shocks from model-year PDF \u2192 one lever per run \u2014 winged vs wingless use different Jacobs and LF tie-down rules", reason: "Hyper Racing public docs: arm length, rack height, and seat position change every starting number" }; var MICRO_SQUARING = { lever: "hyper_micro_squaring", action: "Hyper squaring: rear axle square to frame; Jacobs ladder start hole 3 (far-left frame tab, middle carrier hole on 2021+ tabs) \xB7 rear panhard 6-1/2 in \xB7 X7 use shackle holes to change RH without twisting arm angle", reason: "Hyper Racing public baseline: 1/16 in axle error reads huge on micro tracks \u2014 use squaring kit procedure" }; var MICRO_RH_BLOCKS = { lever: "hyper_micro_rh_blocks", action: "Ride height (Hyper 600 winged): 1-1/2 in blocks all corners (2 in front / 2-1/4 rear wet) \u2014 measure on smallest axle diameter; coil-over ~4\xD7 turns vs torsion stop for same RH change", reason: "Hyper X4-X7 wishbone PDF: block height references assume smallest-diameter axle contact point" }; var MICRO_BARS_NORMAL = { lever: "hyper_micro_bars", action: "Torsion start normal winged 1/6-1/8 (Hyper): LF .675 (+0) / RF .675 (+0) / LR .725 (+0) / RR .750 (+1) \xB7 coils 115 LF / 125 RF if equipped \u2014 verify wishbone vs Z-link PDF", reason: "Hyper Racing public baseline (normal): bar size meaningless without matching arm length \u2014 use Hyper spring-rate calculator if arm length differs" }; var MICRO_BARS_SLICK = { lever: "hyper_micro_bars_slick", action: "Torsion slick winged (Hyper): LF .675 (+1-1/2) / RF .675 (+1) / LR .725 (+1-1/2) / RR .725 (+1-1/2) \xB7 coils +6 LF / +4 RF \xB7 rear panhard 5-1/2 in \xB7 front panhard 3-3/4 in", reason: "Hyper Racing public baseline (slick): stagger band narrows to 5-6 in before shock chasing" }; var MICRO_BARS_WET = { lever: "hyper_micro_bars_wet", action: "Torsion wet winged (Hyper): LF .650 (-1) / RF .675 (+0) / LR .725 (+0) / RR .775 (+0) \xB7 blocks 2 in LF/RF, 2-1/4 LR/RR \xB7 rear panhard 7 in \xB7 stagger 7-9 in", reason: "Hyper Racing public baseline (wet): Jacobs may move to hole 1; RR monotube pressure can run 45 psi per sheet" }; var MICRO_SHOCKS_NORMAL = { lever: "hyper_micro_shocks", action: "Shocks normal winged (Hyper): LF 4/2 \xB7 RF 1/3 \xB7 LR 6/2 \xB7 RR 4.5/3 \xB7 adjustable from full stiff: LF -1 \xB7 RF -2-1/2 \xB7 LR -1 \xB7 RR -2 \xB7 monotube 20/20/15/15 psi", reason: "Hyper Racing public baseline: compression loads weight, rebound unloads \u2014 stabilize psi/stagger before fine-tuning" }; var MICRO_SHOCKS_SLICK = { lever: "hyper_micro_shocks_slick", action: "Shocks slick winged (Hyper): LF 2 \xB7 RF 1/3 \xB7 LR 5/2 \xB7 RR 4.5/3 \xB7 adjustable LF -3 \xB7 RF -5 \xB7 LR -2 \xB7 RR -1/2 or -4 on alt RR \xB7 stagger 5-6 in", reason: "Hyper Racing public baseline (slick): soft RR comp + stiff RF comp is common tighten path on freeing tracks" }; var MICRO_PSI_STAGGER = { lever: "hyper_micro_psi_stagger", action: "Psi/stagger normal winged (Hyper): LF/RF 9 psi \xB7 LR 5-8 \xB7 RR 6.5-10 psi \xB7 rear stagger 5-8.5 in \xB7 tire offset 1 in right \xB7 remeasure hot after laps 8-10", reason: "Hyper Racing public baseline: LR psi changes effective stagger \u2014 log all four hot numbers every session" }; var MICRO_PSI_SLICK = { lever: "hyper_micro_psi_slick", action: "Psi/stagger slick winged (Hyper): LF/RF 9 \xB7 LR 4.5-5.5 \xB7 RR 5.5 psi \xB7 stagger 5-6 in \xB7 Jacobs hole 3 (maybe 5) \u2014 less stagger before wing angle changes", reason: "Hyper Racing public baseline (slick): RS stiffer loosens, LS stiffer tightens \u2014 one psi step per run" }; var MICRO_GEOMETRY = { lever: "hyper_micro_geometry", action: "Geometry winged (Hyper 600): front panhard 3-1/4 in \xB7 rear panhard 6-1/2 in \xB7 RF wishbone middle/bottom hole on X6+ \xB7 top wing ~28 deg on 1/4 mi or smaller (~22 deg big tracks) \xB7 32 in nose wing where legal", reason: "Hyper Racing public baseline: wing angle and panhard move entry balance on winged micro \u2014 not QM/Lightning numbers" }; var MICRO_WINGLESS = { lever: "hyper_micro_wingless", action: "Wingless 600 (Hyper add-on): 50 deg counter-steer minimum \xB7 Jacobs hole 3 or 5 (5 tightens entry) \xB7 pull LR tie-down (full soft possible) \xB7 front shocks easy-up 0.5/2-0.5/3 \xB7 wider front wheels per PDF for scrub/counter-steer", reason: "Hyper Racing public baseline: wingless needs more counter-steer and ladder position \u2014 do not copy winged bar/shock sheet verbatim" }; var MICRO_TIGHTEN = { lever: "hyper_micro_tighten", action: "Hyper dirt tighten: wing back + angle \xB7 less stagger (to ~4.5 in) \xB7 lower RR/LR psi \xB7 RR in \xB7 +1 turn all four RH (small tracks) \xB7 soften RR bar / stiffen RF comp \xB7 LR tie-down out (wingless entry) \u2014 one lever per run", reason: "Hyper Racing public tighten list \u2014 basics before shock stacks" }; var MICRO_LOOSEN = { lever: "hyper_micro_loosen", action: "Hyper dirt loosen: wing forward \xB7 more stagger (62x10 LR, up to 9.5 in) \xB7 raise RF/RR psi \xB7 RR out to 16-1/2 in \xB7 lower RH \xB7 Jacobs hole 1 (tacky rough) \xB7 stiffen RR comp \u2014 one lever per run", reason: "Hyper Racing public loosen list \u2014 wing and stagger not same heat" }; var MICRO_PHASE = { lever: "hyper_micro_phase_tuning", action: "Phase tuning (Hyper 600): entry push \u2192 RF/RR psi down or wing back \xB7 mid push \u2192 stagger down or cross check \xB7 exit loose \u2192 stagger out or RR psi up \xB7 exit push \u2192 RR psi down or RH rear down \u2014 one phase per run", reason: "Hyper Racing public baseline: winged vs wingless use different levers \u2014 diagnose entry/mid/exit before stacking" }; var MICRO_ZLINK = { lever: "hyper_micro_zlink", action: "Hyper Z-link 600: use Z-link setup PDF only \u2014 Jacobs ladder, panhard, and bar lengths differ from X-series wishbone; never copy X7 bar sizes onto Z-link", reason: "Hyper Racing public baseline: suspension architecture changes every starting number" }; var MICRO_X7 = { lever: "hyper_micro_x7", action: "Hyper X7: RR rack offset 3 in right \xB7 shackle 3 in RR / 4 in LR start \xB7 middle carrier hole for baseline blocks; top hole = slick RH, bottom = wet without changing arm angle \xB7 40-400 standoff for shock travel", reason: "Hyper Racing public baseline (X7): shackle hole RH changes are preferred over twisting arm angle" }; var LIGHTNING_PHILOSOPHY = { lever: "hyper_lightning_philosophy", action: "Hyper Lightning philosophy: mechanical checklist first (square, toe 0, caster ~10 deg, RR carrier 4 deg forward, panhard ~4 in) \u2192 winged OR wingless PDF \u2014 never mix sheets; RH measured ground to torsion bar center, no driver", reason: "Hyper Racing Lightning public docs: 13 in midget-scale dirt \u2014 not 600 micro 10 in geometry" }; var LIGHTNING_SQUARING = { lever: "hyper_lightning_squaring", action: "Lightning squaring (Hyper): rack face to rear axle small-diameter spacer = 10-5/16 in on 4-1/4 in blocks \xB7 RR control arm top hole \xB7 LR bottom hole \xB7 LF 3 in outer / 4 in inner, RF 4 in outer / 3 in inner wheels", reason: "Hyper Racing public baseline: squaring reference is rack-to-axle on Lightning, not micro 10 in wheel procedure" }; var LIGHTNING_RH_WINGED = { lever: "hyper_lightning_rh", action: "Ride height winged (Hyper Lightning, no driver to torsion bar center): LF 7-1/2 / RF 8-7/8 / LR 9-1/4 / RR 11-1/8 in \xB7 blocks 3 LF/RF, 3-1/4 LR, 3-1/2 RR on small O.D. axle \xB7 turns off block LF -2 / RF +2 / LR -1/2 / RR +0", reason: "Hyper Racing winged Lightning PDF: front tube marks for squaring consistency" }; var LIGHTNING_RH_WINGLESS = { lever: "hyper_lightning_rh_wingless", action: "Ride height wingless (Hyper Lightning): LF/RF 2-1/2 in blocks \xB7 LR 3 / RR 3-1/4 on small O.D. axle \xB7 turns LF 0 / RF +4 / LR +2 / RR 0 \xB7 wheelbase 71-5/8 in", reason: "Hyper Racing wingless Lightning PDF: lower blocks and different coil turns vs winged sheet" }; var LIGHTNING_BARS_WINGED = { lever: "hyper_lightning_bars", action: "Bars/coil winged (Hyper Lightning): LF 140 / RF 150 \xB7 LR .800 / RR .825 \xB7 stagger 5-10 in (6 in start) \xB7 offsets 3-1/8 in right \xB7 RR track 11-15 in (13 in start)", reason: "Hyper Racing winged Lightning PDF: heavy drivers 220+ use .850 LR / .875 RR and RR out further" }; var LIGHTNING_BARS_WINGLESS = { lever: "hyper_lightning_bars_wingless", action: "Bars/coil wingless (Hyper Lightning): LF 150 / RF 165 \xB7 LR .800 / RR .775 \xB7 stagger 4-10 in (5 in start) \xB7 offsets 2-3/4 in right \xB7 RR track 11-14 in (12 in start)", reason: "Hyper Racing wingless Lightning PDF: lightweight or slick tracks may run .800 RR / .775 LR" }; var LIGHTNING_WINGED = { lever: "hyper_lightning_winged", action: "Winged Lightning (Hyper): Jacobs ladder right-side hole (move left hole on slick, lengthen rod end to hold axle) \xB7 top wing ~26 deg on 1/4 mi or smaller (~18 deg big tracks) \xB7 LF tie-down required \xB7 32 in nose wing", reason: "Hyper Racing winged Lightning PDF \u2014 aero balance is primary on entry; wing back tightens, forward loosens" }; var LIGHTNING_WINGLESS = { lever: "hyper_lightning_wingless", action: "Wingless Lightning (Hyper): Jacobs ladder left-side hole (wet/tight move right hole + lengthen rod end) \xB7 reduce LF tie-down to tighten off corner \xB7 RR soft comp on adj shock common", reason: "Hyper Racing wingless Lightning PDF \u2014 ladder replaces wing as balance lever" }; var LIGHTNING_SHOCKS_WINGED = { lever: "hyper_lightning_shocks_winged", action: "Shocks winged (Hyper): LR full stiff -1-1/2 \xB7 RR -1 \xB7 RF -1-1/2 \xB7 LF -1 turn + LF tie-down \xB7 slick tighten path: RR soft comp + stiff RF comp", reason: "Hyper Racing public baseline: winged uses RF comp + wing before removing LF tie-down" }; var LIGHTNING_SHOCKS_WINGLESS = { lever: "hyper_lightning_shocks_wingless", action: "Shocks wingless (Hyper): LR full stiff -4 \xB7 RR full stiff soft comp on adj RR \xB7 RF -1-1/2 \xB7 LF -4 turns \u2014 loosen LF tie-down to tighten coming off", reason: "Hyper Racing public baseline: wingless exit balance is tie-down + RR platform" }; var LIGHTNING_PSI_WINGED = { lever: "hyper_lightning_psi", action: "Psi winged (Hyper Lightning): LF/RF 10 \xB7 LR 4-10 \xB7 RR 5-12 psi \xB7 American Racer 22.5/7-13 fronts, 23.5-24/10-13 LR, 26/12-13 RR ref \u2014 log hot", reason: "Hyper Racing public baseline: RR band is wide because stagger and offset change effective bite" }; var LIGHTNING_PSI_WINGLESS = { lever: "hyper_lightning_psi_wingless", action: "Psi wingless (Hyper Lightning): LF/RF 10 \xB7 LR 4-6 \xB7 RR 5-8 psi \xB7 same 13 in tire family as winged \u2014 log hot before 1 psi steps", reason: "Hyper Racing wingless PDF: lower LR band than winged because no wing load on entry" }; var LIGHTNING_DIRECTION = { lever: "hyper_lightning_direction", action: "Hyper Lightning dirt lists: tighten = wing back, less stagger, lower RR/LR psi, RR in to 11 in, +1 RS/+2 LS RH, RR soft comp; loosen = wing forward, more stagger, RR out to 14 in, lower RH, Jacobs hole change \u2014 one lever per run", reason: "Hyper Racing public directional guidance \u2014 ordered list, not four-corner hero changes" }; function barsForMicroTrack(track) { if (track === "slick" || track === "drying") return MICRO_BARS_SLICK; if (track === "wet") return MICRO_BARS_WET; return MICRO_BARS_NORMAL; } function shocksForMicroTrack(track) { if (track === "slick" || track === "drying") return MICRO_SHOCKS_SLICK; return MICRO_SHOCKS_NORMAL; } function psiForMicroTrack(track) { if (track === "slick" || track === "drying") return MICRO_PSI_SLICK; return MICRO_PSI_STAGGER; } function hyperMicro600ChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 12; const track = normalizeTrack7(trackState); const wingless = isWingless(enrichedVc); const priors = [ MICRO_PHILOSOPHY, isZLink(enrichedVc) ? MICRO_ZLINK : MICRO_SQUARING, MICRO_RH_BLOCKS, barsForMicroTrack(track), shocksForMicroTrack(track), psiForMicroTrack(track), MICRO_GEOMETRY, wingless ? MICRO_WINGLESS : null, track === "slick" ? MICRO_TIGHTEN : MICRO_LOOSEN, track === "slick" ? null : MICRO_TIGHTEN, MICRO_PHASE ].filter(Boolean); const model = String(enrichedVc.chassis_model || "").toUpperCase(); if (model === "X7" || /x7/.test(String(enrichedVc.chassis_name || ""))) { priors.splice(2, 0, MICRO_X7); } return withCaveat6(priors).slice(0, max); } function hyperLightningChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 9; const wingless = isWingless(enrichedVc); const priors = [ LIGHTNING_PHILOSOPHY, LIGHTNING_SQUARING, wingless ? LIGHTNING_RH_WINGLESS : LIGHTNING_RH_WINGED, wingless ? LIGHTNING_BARS_WINGLESS : LIGHTNING_BARS_WINGED, wingless ? LIGHTNING_WINGLESS : LIGHTNING_WINGED, wingless ? LIGHTNING_SHOCKS_WINGLESS : LIGHTNING_SHOCKS_WINGED, wingless ? LIGHTNING_PSI_WINGLESS : LIGHTNING_PSI_WINGED, LIGHTNING_DIRECTION ]; return withCaveat6(priors).slice(0, max); } function pickHyperPhilosophyLine(profile2) { if (profile2 === "lightning_sprint") return LIGHTNING_PHILOSOPHY.action; return MICRO_PHILOSOPHY.action; } // scripts/lib/experimental/flatKartGrassrootsKnowledge.mjs var FLAT_KART_CAVEAT = "Public baseline from manufacturer documentation and community knowledge \u2014 validate with your own data and specific chassis setup."; function normalizeTrack8(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; return "normal"; } function withCaveat7(priors) { return priors.map((p) => ({ ...p, caveat: FLAT_KART_CAVEAT })); } var FLAT_PHILOSOPHY = { lever: "flat_kart_philosophy", action: "Flat kart (straight-rail) order: square + align \u2192 scales (nose/left/cross) \u2192 seat position \u2192 rear track width / axle slot \u2192 moderate stagger \u2192 psi \u2192 shock fine-tune \u2014 cross and left % run HIGHER than winged outlaw; no wing lever", reason: "Community (dirt oval flat kart): symmetric rail kart \u2014 cross ~60\u201368% and left ~56\u201362% vs outlaw ~52\u201366% cross with wing; seat moves nose and left together" }; var FLAT_VS_OUTLAW = { lever: "flat_vs_outlaw", action: "Flat vs winged outlaw: flat karts use higher cross + higher left + moderate inch stagger \xB7 outlaw uses lower cross family + wing + cage seat cradle \u2014 do not copy QRC/Toigo outlaw scale sheets onto LO206/flat dirt oval", reason: "Community (No Goats + flat kart scaling): most common mistake is Pursuit/outlaw cross on straight-rail flat kart" }; var FLAT_VS_MICRO = { lever: "flat_vs_micro", action: "Flat kart vs micro sprint: flat kart tunes cross % and rear track width \xB7 micro uses blocks/torsion/panhard/wing \u2014 not interchangeable hard numbers", reason: "Class separation: flat dirt oval kart priors are not Hyper micro or QM geometry" }; var FLAT_LOGGING = { lever: "flat_kart_logging", action: "Flat kart log sheet: date \xB7 track state \xB7 cross/left/nose % \xB7 seat hole \xB7 rear track width \xB7 stagger \xB7 cold/hot psi \xB7 one change per run \u2014 your A-B-A log replaces these priors fastest", reason: "Community (grassroots flat kart): logged measurements beat generic bands within 2\u20133 sessions at the same track" }; var FLAT_SCALING_TACKY = { lever: "flat_kart_scaling", action: "Scale tacky/normal (flat kart community): cross ~62\u201368% \xB7 left ~56\u201362% \xB7 nose ~42\u201348% via seat fore/aft \xB7 get nose/left within ~0.5% before cross washers or spindle shims", reason: "Community (LO206/flat dirt oval): higher cross than winged outlaw \u2014 straight-rail kart needs left side weight for RS bite" }; var FLAT_CROSS_MECHANICS = { lever: "flat_kart_cross_mechanics", action: "Cross mechanics (flat kart): RF spindle up / washer above = cross up \xB7 rear axle down (chassis up) \u2248 +0.5\u20131% cross \xB7 front stagger changes cross \xB7 seat forward adds nose and affects cross feel \u2014 log on YOUR scales", reason: "Community (flat kart scaling): cross is fine-tune AFTER nose/left; wrong order teaches bad habits" }; var FLAT_SEAT = { lever: "flat_kart_seat", action: "Seat position (flat kart): forward \u2192 more nose bite, often tighter entry \xB7 back \u2192 loose entry \xB7 left-side ballast low on seat rails \xB7 never loose shot in frame \xB7 seat hole logged every scale night", reason: "Community (LO206/flat kart): seat is primary nose AND left lever on straight-rail \u2014 not cage cradle mm like outlaw" }; var FLAT_LEFT_SIDE = { lever: "flat_kart_left_side", action: "Left side % (flat kart): target ~56\u201362% dirt oval \xB7 flat track \u2192 more left \xB7 banked 1/8 \u2192 may run slightly less left vs flat \xB7 wet often wants lower left + lower cross pairing", reason: "Community (flat kart): left % sets how hard RS tires work \u2014 flat karts run higher left than winged outlaw low-cross family" }; var FLAT_NOSE = { lever: "flat_kart_nose", action: "Nose weight (flat kart): ~42\u201348% with driver in rule \xB7 pushing/tight entry \u2192 seat forward 1 hole \xB7 loose entry \u2192 seat back \xB7 fix nose on scales before cross chase", reason: "Community (flat kart): inadequate nose reads as push \u2014 seat before hero cross moves" }; var FLAT_SQUARING = { lever: "flat_kart_squaring", action: "Squaring (flat kart): level scales \xB7 RF toe 0 \xB7 LF ~1/16 in out \xB7 rear axle level side-to-side \xB7 kingpin diagonal check \xB7 re-align after spindle washer cross change \xB7 torque consistently", reason: "Community (flat kart + CRG/Laser norms): geometry first \u2014 bent rail or toe drift mimics bad cross" }; var FLAT_GEOMETRY = { lever: "flat_kart_geometry", action: "Front end (flat kart community): caster split ~2\xB0 \xB7 RF caster ~8\u201312\xB0 dirt start \xB7 RF camber ~-2.5 to -3\xB0 \xB7 LF ~+0.5 to +1\xB0 \xB7 wider front track than outlaw offset \u2014 symmetric rail setup", reason: "Community (flat kart): not narrow-RF outlaw geometry \u2014 full-width flat kart front end" }; var FLAT_REAR_TRACK = { lever: "flat_kart_rear_track", action: "Rear track width (flat kart major lever): typical ~39\u201342 in dirt oval \xB7 wider rear often loosens \xB7 narrower tightens \xB7 2\u20134 mm step per run \xB7 log width with cross every night \u2014 primary lever on straight-rail vs outlaw axle slot", reason: "Community (LO206/flat kart): rear track width is the flat-kart equivalent of outlaw rear axle fore/aft slot" }; var FLAT_RIDE_HEIGHT = { lever: "flat_kart_ride_height", action: "Ride height / rake (flat kart): keep CG low \xB7 rear ride height up frees kart \xB7 lower rear adds grip \xB7 torsion/coil turns move cross feel \u2014 set baseline ride height before cross washer chase", reason: "Community (flat kart): CGH changes transfer \u2014 one ride-height turn can read like 0.5% cross" }; var FLAT_STAGGER_BASE = { lever: "flat_kart_stagger", action: "Stagger tacky/normal (flat kart): front ~1\u20131.5 in \xB7 rear ~3/4\u20132 in on 1/8 mi (1/5 mi upper rear band) \xB7 moderate vs outlaw \u2014 middle rotation lever \xB7 1/8 in rear step \xB7 log hot circumference after psi stable", reason: "Community (flat kart): inch stagger matters but usually more moderate than micro sprint \u2014 cross/seat first on scale" }; var FLAT_PSI = { lever: "flat_kart_psi", action: "Psi (flat kart / LO206 community): LF 8\u201310 \xB7 RF 10\u201312 \xB7 LR 6\u20138 \xB7 RR 8\u201311 starting band \xB7 sealed tread log hot lap 8\u201310 \xB7 1/4 psi steps after compound correct \xB7 RS stiffer loosens exit", reason: "Community (LO206 dirt oval): psi shifts effective cross \u2014 tires before shock stacks on race night" }; var FLAT_SHOCKS = { lever: "flat_kart_shocks", action: "Shocks (flat kart community): stabilize psi/stagger/cross first \xB7 entry push \u2192 check nose/seat before shock \xB7 exit loose \u2192 RF rebound / LR comp band \xB7 slick = soften RR-side after cross cut if exit binds", reason: "Community (flat kart): shock fine-tune after scale family \u2014 not first lever on dirt oval flat kart" }; var FLAT_TIGHTEN = { lever: "flat_kart_direction_tight", action: "Tighten (flat kart): cross down 0.3\u20130.5% \xB7 less rear stagger \xB7 seat forward if entry push \xB7 rear track in 2 mm \xB7 lower RR psi 1/4 \xB7 RF camber more negative if center push", reason: "Community (flat kart): cross + stagger + seat before second cross \u2014 one lever per run" }; var FLAT_LOOSEN = { lever: "flat_kart_direction_loose", action: "Loosen (flat kart): cross up 0.5% if LR unloaded \xB7 more rear stagger \xB7 seat back if loose entry \xB7 rear track out 2 mm \xB7 raise RF/RR psi 1/4 if tread allows", reason: "Community (flat kart): loose entry often nose too low or cross too high for grip state" }; var FLAT_PHASE = { lever: "flat_kart_phase_tuning", action: "Phase tuning (flat kart): seat forward \u2192 entry push \xB7 seat back \u2192 loose entry \xB7 cross down \u2192 center/entry \xB7 rear stagger \u2192 middle \xB7 rear track width \u2192 mid/exit \u2014 diagnose phase before stacking levers", reason: "Community (flat kart): straight-rail dirt oval \u2014 entry vs exit vs middle use different levers; no wing on flat kart" }; var FLAT_DRYING = { lever: "flat_kart_drying", action: "Drying transition (flat kart): cross down 0.3\u20130.5% before rear stagger add \xB7 rear stagger down 1/8 in per run \xB7 re-scale on race tires \xB7 seat tweak before second cross cut", reason: "Community (flat kart): tacky \u2192 drying = less cross/stagger before rear track width chase" }; var FLAT_TRACK_STATE = { lever: "flat_kart_track_state", action: "Track state (flat kart): wet/soft \u2192 lower left + lower cross \xB7 tacky \u2192 baseline cross/left band \xB7 drying \u2192 less cross/stagger \xB7 slick \u2192 cross down 1\u20132% from tacky, moderate stagger \u2014 log every transition", reason: "Community (dirt oval flat kart): percentages shift with grip \u2014 baseline is a band not a constant" }; function crossForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "flat_kart_cross_track", action: "Cross slick/drying (flat kart): target ~58\u201364% \u2014 drop 0.5\u20131% from tacky before stagger add \xB7 heavy cross + slick binds exit on straight-rail", reason: "Community (flat kart): freeing track wants less cross vs tacky 62\u201368% window" }; } if (track === "wet") { return { lever: "flat_kart_cross_track", action: "Cross wet/heavy (flat kart): ~60\u201365% mid-band conservative \xB7 pair lower left if RS overheats \xB7 avoid max cross + max stagger same heat", reason: "Community (flat kart): soft tracks stack grip \u2014 conservative cross first laps" }; } return FLAT_SCALING_TACKY; } function leftForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "flat_kart_left_track", action: "Left % slick/drying (flat kart): often ~55\u201358% mid band \xB7 pair with lower cross \xB7 flat track may keep more left than banked 1/8", reason: "Community (flat kart): freed grip \u2014 less left + less cross together" }; } if (track === "wet") { return { lever: "flat_kart_left_track", action: "Left % wet/heavy (flat kart): lower left ~54\u201358% \xB7 conservative with cross mid-band \xB7 RS overheating = left still high for moisture", reason: "Community (flat kart): wet often wants lower left + lower cross pairing" }; } return FLAT_LEFT_SIDE; } function staggerForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "flat_kart_stagger_track", action: "Stagger slick/drying (flat kart): front ~1\u20131.25 in \xB7 rear ~3/4\u20131.25 in \u2014 take rear out before cross add \xB7 less stagger tightens as track frees", reason: "Community (flat kart): slick transition = less rear stagger before cross on dirt oval flat kart" }; } if (track === "wet") { return { lever: "flat_kart_stagger_track", action: "Stagger wet/heavy (flat kart): front ~1.25\u20131.5 in \xB7 rear ~1\u20132 in upper if loose \xB7 conservative cross/stagger pairing first laps", reason: "Community (flat kart): moisture \u2014 stagger after cross family set" }; } return FLAT_STAGGER_BASE; } function psiForTrack2(track) { if (track === "slick" || track === "drying") { return { lever: "flat_kart_psi_track", action: "Psi slick/drying (flat kart): RS hot toward lower band \xB7 RR/LR down 1/4 psi before cross up when exit binds \xB7 log lap 8\u201310 hot on sealed tread", reason: "Community (LO206/flat kart): slick = lower RS band before cross chase" }; } if (track === "wet") { return { lever: "flat_kart_psi_track", action: "Psi wet/heavy (flat kart): RS upper band if loose exit after conservative cross \xB7 still 1/4 psi steps \xB7 match stagger circumference before hero moves", reason: "Community (flat kart): wet allows higher RS before seat/cross stack" }; } return FLAT_PSI; } function rideHeightForTrack3(track) { if (track === "slick" || track === "drying") { return { lever: "flat_kart_rh_track", action: "Ride height slick/drying (flat kart): raise rear slightly to free \xB7 set baseline before cross cut \xB7 one RH turn \u2248 cross feel change \u2014 log with scale", reason: "Community (flat kart): freed grip \u2014 RH before second cross washer" }; } if (track === "wet") { return { lever: "flat_kart_rh_track", action: "Ride height wet (flat kart): lower rear adds grip first laps \xB7 conservative before cross max \xB7 torsion/coil turn logged with cross", reason: "Community (flat kart): moisture stacks mechanical grip \u2014 RH + cross pairing" }; } return FLAT_RIDE_HEIGHT; } function shocksForTrack(track) { if (track === "slick" || track === "drying") { return { lever: "flat_kart_shocks_track", action: "Shocks slick/drying (flat kart): soften RR comp / stiffen RF comp common tighten after cross cut \xB7 no full stiff stacks until psi/stagger set", reason: "Community (flat kart): shock after scale family on freeing dirt oval" }; } return FLAT_SHOCKS; } function directionForTrack(track) { if (track === "slick") return [FLAT_PHASE, FLAT_TIGHTEN, FLAT_LOGGING]; if (track === "drying") return [FLAT_DRYING, FLAT_PHASE, FLAT_TIGHTEN]; if (track === "wet") return [FLAT_PHASE, FLAT_LOOSEN, FLAT_TIGHTEN]; return [FLAT_PHASE, FLAT_LOOSEN, FLAT_TIGHTEN, FLAT_LOGGING]; } function flatKartTrackCore(track) { return [ FLAT_TRACK_STATE, crossForTrack(track), leftForTrack(track), FLAT_NOSE, FLAT_SEAT, FLAT_CROSS_MECHANICS, staggerForTrack(track), FLAT_REAR_TRACK, rideHeightForTrack3(track), psiForTrack2(track), shocksForTrack(track), ...directionForTrack(track) ]; } var BRAND_PRIORS = { laser: { lever: "flat_kart_brand_laser", action: "Laser flat kart (community): straight-rail dirt oval \u2014 use Laser dealer setup sheet if available \xB7 generic flat cross/left bands apply until sheet in hand", reason: "Community (Laser flat kart): manufacturer detail thin publicly \u2014 generic flat kart scaling order still applies" }, prowler: { lever: "flat_kart_brand_prowler", action: "Prowler flat kart (community): verify Prowler baseline sheet \xB7 rear track width + cross % primary on dirt oval \u2014 do not copy outlaw winged numbers", reason: "Community (Prowler): brand-specific arm length may shift bar size \u2014 generic flat priors are start band" }, emmick: { lever: "flat_kart_brand_emmick", action: "Emmick flat kart (community): Emmick flat vs Emmick micro are different classes \u2014 use flat dirt oval cross ~62\u201368% band, not Hyper micro PDF", reason: "Community: Emmick name appears in multiple classes \u2014 confirm flat kart vs micro sprint in Garage" }, triton: { lever: "flat_kart_brand_triton", action: "Triton flat kart (community): straight-rail scaling like LO206 norms \xB7 seat + rear track width before cross washer stacks", reason: "Community (Triton flat kart): limited public geometry \u2014 generic flat kart order applies" }, crg: { lever: "flat_kart_brand_crg", action: "CRG flat kart (community): CRG dirt oval often seat-forward-first on push \xB7 hot psi log lap 8\u201310 on sealed tread \xB7 left 56\u201362% band", reason: "Community (CRG flat/LO206): CRG-specific sheet when available \u2014 generic flat priors until then" }, coyote: { lever: "flat_kart_brand_coyote", action: "Coyote flat kart (community): axle/width sensitivity on some Coyote dirt setups \u2014 psi before second width step \xB7 generic flat cross band start", reason: "Community (Coyote flat kart): geometry differs from CRG \u2014 validate width steps on your chassis" }, otk: { lever: "flat_kart_brand_otk", action: "OTK flat kart (community): OTK asphalt habits do not transfer \u2014 dirt oval cross higher than road racing \xB7 use flat kart priors not winged outlaw", reason: "Community (OTK): brand often road-focused publicly \u2014 dirt flat oval uses this generic layer" } }; var PHILOSOPHY_LINES3 = { default: { slick: "Flat kart slick: cross ~58\u201364% down from tacky \xB7 rear stagger out before cross add \xB7 not outlaw low-cross or wing lever", wet: "Flat kart wet: conservative cross/left mid-band \xB7 seat + rear track width before washer cross", default: FLAT_PHILOSOPHY.action }, laser: { default: "Laser flat kart: dealer sheet when available \u2014 generic flat cross/left/scaling order until then" }, prowler: { default: "Prowler flat kart: rear track width + cross primary \u2014 outlaw winged sheets do not apply" }, emmick: { default: "Emmick flat kart: confirm flat vs micro in Garage \u2014 flat dirt oval cross runs higher than winged outlaw" }, triton: { default: "Triton flat kart: seat + rear track before cross washers \u2014 straight-rail dirt oval norms" }, crg: { default: "CRG flat/LO206: seat-forward for push \xB7 hot psi lap 8\u201310 \xB7 left 56\u201362% band on dirt oval" }, coyote: { default: "Coyote flat kart: psi before second rear width step \u2014 validate on your Coyote geometry" }, otk: { default: "OTK dirt flat oval: higher cross than road setup \u2014 use flat kart priors not outlaw winged" } }; function flatKartChassisPriors(enrichedVc = {}, trackState, opts = {}) { const max = opts.maxPriors ?? 20; const track = normalizeTrack8(trackState); const mfrKey = enrichedVc.chassis_mfr_key; const priors = [ FLAT_PHILOSOPHY, FLAT_VS_OUTLAW, FLAT_VS_MICRO, FLAT_SQUARING, FLAT_GEOMETRY, ...BRAND_PRIORS[mfrKey] ? [BRAND_PRIORS[mfrKey]] : [], ...flatKartTrackCore(track) ]; return withCaveat7(priors).slice(0, max); } function pickFlatKartPhilosophyLine(mfrKey, trackState) { const track = normalizeTrack8(trackState); const brandLines = PHILOSOPHY_LINES3[mfrKey] || {}; const defaultLines = PHILOSOPHY_LINES3.default; if ((track === "slick" || track === "drying") && (brandLines.slick || defaultLines.slick)) { return brandLines.slick || defaultLines.slick; } if (track === "wet" && (brandLines.wet || defaultLines.wet)) { return brandLines.wet || defaultLines.wet; } return brandLines.default || defaultLines.default; } // scripts/lib/experimental/grassrootsChassisRouting.mjs var MFR_PATTERNS = { quarter_midget: [ { key: "stanley", re: /stanley|rsr/, mfr: "Stanley / RSR" }, { key: "bullrider", re: /bullrider|bull rider/, mfr: "Bullrider" }, { key: "nc", re: /\bnc chassis\b|\bnc\b/, mfr: "NC Chassis" }, { key: "sherman", re: /sherman/, mfr: "Sherman" } ], outlaw_kart: [ { key: "slack", re: /slack|reactor|axiom|xpect|pursuit|elevate|pmc/, mfr: "Slack Performance (PMC)" }, { key: "toigo", re: /toigo|stealth|wraith/, mfr: "Toigo" }, { key: "phantom", re: /phantom|minecon/, mfr: "Phantom MINecon" }, { key: "qrc", re: /qrc|ignite/, mfr: "QRC Karts" }, { key: "ultramax", re: /ultramax|rival|evolve/, mfr: "Ultramax" }, { key: "chaos", re: /chaos|carlson|premier|charger|millennium|millenium/, mfr: "Chaos / Carlson" } ], flat_kart: [ { key: "laser", re: /laser/, mfr: "Laser" }, { key: "prowler", re: /prowler/, mfr: "Prowler" }, { key: "emmick", re: /emmick/, mfr: "Emmick" }, { key: "triton", re: /triton/, mfr: "Triton" }, { key: "crg", re: /\bcrg\b/, mfr: "CRG" }, { key: "coyote", re: /coyote/, mfr: "Coyote" }, { key: "otk", re: /\botk\b/, mfr: "OTK" } ], micro_sprint_600: [ { key: "d1", re: /\bd1\b|d1 driven|driven performance|d1driven/, mfr: "D1/Driven Performance" }, { key: "ten_j", re: /ten[\s-]?j|tenj\b/, mfr: "Ten J" }, { key: "stallard", re: /stallard|\bsst\b/, mfr: "Stallard" }, { key: "pace", re: /\bpace\b|\bpmp\b|pace race/, mfr: "Pace/PMP" }, { key: "sawyer", re: /sawyer|\b626\b/, mfr: "Sawyer" }, { key: "factor1", re: /factor[\s.-]?1|factor one/, mfr: "Factor 1" }, { key: "hyper", re: /hyper|x4|x5|x6|x7|z-link|z link|emmick|kiwi|spike|dmi|bell|eagle/, mfr: "Hyper Racing" }, { key: "emmick", re: /emmick/, mfr: "Emmick" }, { key: "spike", re: /spike/, mfr: "Spike" } ], lightning_sprint: [ { key: "hyper", re: /hyper|lightning/, mfr: "Hyper Racing" }, { key: "saldana", re: /saldana/, mfr: "Saldana" } ], hyper_midget: [ { key: "hyper", re: /hyper|emmick|kiwi|spike|dmi|bell/, mfr: "Hyper Racing" }, { key: "eagle", re: /eagle|spike midget/, mfr: "Eagle / Spike Midget" } ] }; var MODEL_PATTERNS = { hyper: [ { model: "X7", re: /\bx7\b|x-7/ }, { model: "X6", re: /\bx6\b|x-6/ }, { model: "X5", re: /\bx5\b|x-5/ }, { model: "X4", re: /\bx4\b|x-4/ }, { model: "Z-Link", re: /z-link|z link|zlink/ }, { model: "Lightning", re: /lightning/ } ], toigo: [ { model: "Stealth X", re: /stealth\s*x|stealthx/ }, { model: "Wraith", re: /wraith/ } ], stanley: [ { model: "RSR", re: /rsr/ } ] }; var MANUFACTURER_PRIORS = { quarter_midget: { stanley: { action: "Stanley/RSR: use their public baseline \u2014 square \u2192 RF/RR ~11 psi, LF/LR ~5 psi, ~1.5 in ride height (no driver), cross ~54% (X-method)", reason: "public baseline (QM \xB7 Stanley/RSR): squaring and ride-height reference points are Stanley-specific \u2014 do not copy NC/Bullrider measurements", measurement: "Measure ride height and cross the same way every time (with or without driver) per Stanley sheet" }, bullrider: { action: "Bullrider: start from Bullrider official baseline sheet before any change \u2014 community norm for this brand", reason: "public baseline (QM \xB7 Bullrider): panhard/birdcage/squaring references differ from Stanley \u2014 use Bullrider measurement locations" }, nc: { action: "NC Chassis: use NC baseline for panhard, birdcage, and squaring reference points \u2014 not interchangeable with Stanley", reason: "public baseline (QM \xB7 NC): NC measurement locations differ from Stanley/RSR public guides" }, sherman: { action: "Sherman: verify Sherman-specific baseline / PitLogic codes for your track before custom tuning", reason: "public baseline (QM \xB7 Sherman): scaling bands similar but squaring references differ by builder" } }, outlaw_kart: { slack: { action: "Slack (PMC public guide): baseline nose/left/cross scaling \u2014 heavier driver or more grip usually wants less cross built in", reason: "public baseline (outlaw \xB7 Slack): Pursuit/Reactor scaling philosophy \u2014 offset cage, not flat-kart width math" }, toigo: { action: "Toigo Stealth/Wraith: tune cross %, seat cradle (mm), and rear stagger \u2014 scaling videos over symmetric track-width tools", reason: "public baseline (outlaw \xB7 Toigo): offset ladder frame \u2014 seat cradle moves bite and effective cross" }, phantom: { action: "Phantom MINecon: roll speed + caster blocks + seat cradle flex \u2014 wing rail angle/position moves downforce on winged outlaws", reason: "public baseline (outlaw \xB7 Phantom): different from QRC symmetric clone geometry" }, qrc: { action: "QRC outlaw: cross, wing, and inch stagger primary \u2014 verify local wing/size limits on your rulebook", reason: "public baseline (outlaw \xB7 QRC): offset dirt oval scene \u2014 not LO206 rear track-width primary" }, ultramax: { action: "Ultramax Rival/Evolve: model-specific cross/stagger band first \u2014 confirm Evolve vs Rival notes for your track", reason: "public baseline (outlaw \xB7 Ultramax): scaling varies by model generation" }, chaos: { action: "Chaos/Carlson T-18 tread setups: cross may sit high 50s\u2013low 60s \u2014 sidewall flex limits cross vs slick-prepped karts", reason: "public baseline (outlaw \xB7 tread-heavy): different cross window than pink-slick outlaw prepped fields" } }, flat_kart: { laser: { action: "Laser flat kart: dealer setup sheet when available \u2014 generic flat cross ~62\u201368% / left ~56\u201362% until sheet in hand", reason: "public baseline (flat kart \xB7 Laser): straight-rail dirt oval \u2014 not winged outlaw scaling" }, prowler: { action: "Prowler flat kart: rear track width + cross % primary on dirt oval \u2014 verify Prowler baseline before outlaw numbers", reason: "public baseline (flat kart \xB7 Prowler): brand public detail thin \u2014 generic flat priors apply" }, emmick: { action: "Emmick flat kart: confirm flat vs micro sprint in Garage \u2014 flat dirt oval cross runs higher than winged outlaw", reason: "public baseline (flat kart \xB7 Emmick): Emmick name used in multiple classes \u2014 flat kart priors not Hyper micro PDF" }, triton: { action: "Triton flat kart: seat + rear track width before cross washers \u2014 straight-rail dirt oval norms", reason: "public baseline (flat kart \xB7 Triton): limited public geometry \u2014 generic flat scaling order" }, crg: { action: "CRG flat/LO206: seat-forward for push \xB7 hot psi lap 8\u201310 on sealed tread \xB7 left 56\u201362% dirt oval band", reason: "public baseline (flat kart \xB7 CRG): CRG-specific sheet when available \u2014 generic flat layer until then" }, coyote: { action: "Coyote flat kart: psi before second rear width step \u2014 validate width changes on Coyote geometry", reason: "public baseline (flat kart \xB7 Coyote): axle/width sensitivity on some Coyote dirt setups" }, otk: { action: "OTK dirt flat oval: higher cross than road setup \u2014 use flat kart priors not winged outlaw or asphalt habits", reason: "public baseline (flat kart \xB7 OTK): road-focused brand \u2014 dirt flat oval uses generic flat layer" } }, micro_sprint_600: { d1: { action: "D1/Driven Performance: square rear per D1 PDF (12.5 in LR arm rod method, 90\xB0 birdcages) before scale \u2014 trailing-arm family, not Hyper wishbone", reason: "public baseline (600 micro \xB7 D1): D1 Driven squaring guide + trailing-arm community refs" }, ten_j: { action: "Ten J: dealer setup sheets first \u2014 trailing-arm micro with private baseline; use Ten J squaring before PMP/Hyper numbers", reason: "public baseline (600 micro \xB7 Ten J): setup sheets private \u2014 community trailing-arm order until builder sheet" }, stallard: { action: "Stallard: EMi squaring guides \u2014 LF 11 in / RF 9.5 in torsion-to-axle, caster 15\u201320\xB0 built-in \xB7 rear square per Stallard sheet", reason: "public baseline (600 micro \xB7 Stallard): Stallard-specific front geometry vs PMP upright measure" }, pace: { action: "Pace/PMP 600: official PMP setup sheet squaring + ride height to torsion bar center \u2014 strongest published trailing-arm baseline", reason: "public baseline (600 micro \xB7 Pace/PMP): manufacturer PMP sheet \u2014 not Hyper X-series" }, sawyer: { action: "Sawyer 626: wishbone micro \u2014 SawyerChassis FAQ setup sheet + axle spacing diagram; do not use trailing-arm PMP front measures", reason: "public baseline (600 micro \xB7 Sawyer): 626 wishbone platform \u2014 different squaring family from Stallard/PMP" }, factor1: { action: "Factor 1: Chassis 101 shock matrix + tight-in-loose-off philosophy \xB7 trailing-arm squaring community start until builder sheet", reason: "public baseline (600 micro \xB7 Factor 1): manufacturer blog fundamentals \u2014 validate geometry on your chassis" }, hyper: { action: "Hyper 600 micro: use model-year guide (X4\u2013X7 wishbone, Z-link, torsion, coil) \u2014 blocks and bar lengths are chassis-specific", reason: "public baseline (600 micro \xB7 Hyper): rack height and torsion arm length change block/torsion starting points", models: { X7: "Hyper X7: January 2026 wishbone setup PDF \u2014 1-1/2 in blocks, ~5\u20138.5 in stagger band on 10 in wheels", X6: "Hyper X6: X4\u2013X7 wishbone family \u2014 verify wishbone vs Z-link PDF before copying bar sizes", X5: "Hyper X5: wishbone guide stagger/psi bands \u2014 remeasure hot after laps 8\u201310", X4: "Hyper X4: entry-level wishbone geometry \u2014 square before ride height per Hyper squaring kit", "Z-Link": "Hyper Z-link 600: separate Z-link setup PDF \u2014 Jacobs ladder and panhard differ from wishbone X-series" } }, emmick: { action: "Emmick micro: builder baseline first \u2014 Hyper directional guidance (stagger, psi, wing) as principles only", reason: "public baseline (600 micro \xB7 Emmick): public detail limited vs Hyper \u2014 validate every number on your car" }, spike: { action: "Spike micro: start from Spike builder sheet \u2014 use Hyper principles for direction, not hard numbers", reason: "public baseline (600 micro \xB7 Spike): measurement points may differ from Hyper X-series" } }, lightning_sprint: { hyper: { action: "Hyper Lightning: winged vs wingless PDF \u2014 ride height to torsion bar center (no driver), 13 in wheels", reason: "public baseline (lightning \xB7 Hyper): not 600 micro 10 in geometry \u2014 separate Lightning squaring references", models: { Lightning: "Hyper Lightning chassis: Jacobs ladder right hole (winged) or left hole (wingless) per Hyper sheet" } }, saldana: { action: "Saldana lightning: verify builder baseline \u2014 Hyper Lightning directional lists apply as principles when sheet missing", reason: "public baseline (lightning \xB7 Saldana): public detail limited \u2014 log your builder starting sheet" } }, hyper_midget: { hyper: { action: "Hyper midget: use Hyper midget setup guide bands \u2014 stagger ~4 in start, verify on your ladder/bar config", reason: "public baseline (midget \xB7 Hyper): full-size midget priors \u2014 not 600 micro or lightning hard numbers" }, eagle: { action: "Eagle/Spike midget: panhard and torsion dominate on small tracks \u2014 builder sheet when available", reason: "public baseline (midget \xB7 Eagle): stagger secondary to LR bite on 1/4 mi bullrings" } } }; function norm(s) { return String(s || "").toLowerCase().trim(); } function combinedChassisText(vc2 = {}) { return [ vc2.chassis_name, vc2.chassis_mfr, vc2.chassis_model, vc2.chassis_manufacturer ].filter(Boolean).join(" "); } function detectGrassrootsChassisManufacturer(profile2, vc2 = {}) { const text = norm(combinedChassisText(vc2)); if (!text) return { key: null, mfr: null, confidence: "missing" }; const patterns = MFR_PATTERNS[profile2] || []; for (const p of patterns) { if (p.re.test(text)) { return { key: p.key, mfr: p.mfr, confidence: "high" }; } } if (vc2.chassis_philosophy) { return { key: "philosophy", mfr: vc2.chassis_mfr || "Chassis", confidence: "medium" }; } return { key: null, mfr: null, confidence: "missing" }; } function detectGrassrootsChassisModel(mfrKey, vc2 = {}) { const text = norm(combinedChassisText(vc2)); const patterns = MODEL_PATTERNS[mfrKey] || []; for (const p of patterns) { if (p.re.test(text)) return { model: p.model, generation: p.model }; } if (vc2.chassis_model) return { model: String(vc2.chassis_model), generation: null }; return { model: null, generation: null }; } function enrichGrassrootsVehicleContext(vc2 = {}, profile2 = "") { const mfr = detectGrassrootsChassisManufacturer(profile2, vc2); const modelInfo = mfr.key ? detectGrassrootsChassisModel(mfr.key, vc2) : { model: null, generation: null }; const profileKey = mfr.key ? modelInfo.model ? `${mfr.key}_${modelInfo.model.toLowerCase().replace(/[\s-]+/g, "_")}` : mfr.key : null; const measurements = vc2.setup_measurements && typeof vc2.setup_measurements === "object" ? { ...vc2.setup_measurements } : null; return { ...vc2, chassis_manufacturer: mfr.mfr || vc2.chassis_mfr || null, chassis_model: modelInfo.model || vc2.chassis_model || null, chassis_generation: modelInfo.generation || vc2.chassis_generation || null, chassis_profile_key: profileKey, chassis_mfr_key: mfr.key, chassis_routing_confidence: mfr.confidence, chassis_model_missing: mfr.confidence === "missing", setup_measurements: measurements, setup_style: vc2.setup_style || vc2.front_susp || null, front_susp: vc2.front_susp || vc2.setup_style || null }; } function measurementContextPrior(vc2) { const m = vc2.setup_measurements; if (!m || typeof m !== "object") return null; const parts = []; if (m.lf_psi != null || m.rf_psi != null) parts.push(`psi LF ${m.lf_psi ?? "?"}/RF ${m.rf_psi ?? "?"}`); if (m.stagger != null) parts.push(`stagger ${m.stagger}"`); if (m.left_pct != null && m.rear_pct != null) parts.push(`left ${m.left_pct}% rear ${m.rear_pct}%`); if (m.ride_ht_f != null || m.ride_ht_r != null) parts.push(`ride F ${m.ride_ht_f ?? "?"}/R ${m.ride_ht_r ?? "?"}`); if (m.j_ladder) parts.push(`Jacobs ${m.j_ladder}`); if (m.panhard != null) parts.push(`panhard ${m.panhard}`); if (m.seat_pos != null) parts.push(`seat ${m.seat_pos}mm`); if (m.rear_track_width_mm != null) parts.push(`rear track ${m.rear_track_width_mm}mm`); if (!parts.length) return null; return { lever: "logged_setup", action: `Logged setup on file: ${parts.join(" \xB7 ")} \u2014 use as tonight's A-B-A reference baseline`, reason: "grassroots chassis context: user-reported measurements anchor recommendations to this car's current state" }; } function modsContextPrior(vc2) { const mods = [vc2.geo_notes, vc2.user_mods, vc2.trait_notes].filter(Boolean).join(" | "); if (!mods.trim()) return null; const short = mods.length > 120 ? `${mods.slice(0, 117)}...` : mods; return { lever: "user_mods", action: `Your notes/mods on file: ${short}`, reason: "grassroots chassis context: geo notes and mods may shift baseline targets \u2014 validate moves against your sheet" }; } function grassrootsChassisManufacturerPriors(profile2, enrichedVc = {}, trackState) { if (!profile2) return []; if (profile2 === "flat_kart") { const priors2 = flatKartChassisPriors(enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (enrichedVc.chassis_routing_confidence === "missing") return []; const mfrKey = enrichedVc.chassis_mfr_key; if (!mfrKey || mfrKey === "philosophy") { if (enrichedVc.chassis_philosophy) { return [{ lever: "chassis_mfr", action: enrichedVc.chassis_philosophy.split(".")[0], reason: `public baseline (${profile2}): ${enrichedVc.chassis_philosophy}` }]; } return []; } if (profile2 === "quarter_midget" && mfrKey === "stanley") { const priors2 = stanleyRsrChassisPriors(enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (profile2 === "quarter_midget" && mfrKey === "bullrider") { const priors2 = bullriderQuarterMidgetChassisPriors(enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (profile2 === "quarter_midget" && mfrKey === "nc") { const priors2 = ncQuarterMidgetChassisPriors(enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (profile2 === "micro_sprint_600" && isMicroNonHyperDeepBuilder(mfrKey)) { const priors2 = microSprint600ChassisPriors(mfrKey, enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (profile2 === "micro_sprint_600" && mfrKey === "hyper") { const priors2 = hyperMicro600ChassisPriors(enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (profile2 === "lightning_sprint" && mfrKey === "hyper") { const priors2 = hyperLightningChassisPriors(enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } if (profile2 === "outlaw_kart" && isOutlawDeepBuilder(mfrKey)) { const priors2 = outlawKartChassisPriors(mfrKey, enrichedVc, trackState); const meas2 = measurementContextPrior(enrichedVc); if (meas2) priors2.push(meas2); const modP2 = modsContextPrior(enrichedVc); if (modP2) priors2.push(modP2); return priors2; } const entry = MANUFACTURER_PRIORS[profile2]?.[mfrKey]; if (!entry) return []; const priors = []; let action = entry.action; let reason = entry.reason; const model = enrichedVc.chassis_model; if (model && entry.models?.[model]) { action = entry.models[model]; reason = `${entry.reason} \xB7 Model: ${model}`; } else if (model) { action = `${action} (your model: ${model})`; reason = `${entry.reason} \xB7 Confirm ${model}-specific sheet from builder`; } if (entry.measurement) { priors.push({ lever: "chassis_measurement", action: entry.measurement, reason: `${reason} \u2014 measurement points are manufacturer-specific` }); } priors.unshift({ lever: "chassis_mfr", action, reason }); const meas = measurementContextPrior(enrichedVc); if (meas) priors.push(meas); const modP = modsContextPrior(enrichedVc); if (modP) priors.push(modP); return priors; } function resolveGrassrootsChassisHint(profile2, vc2 = {}, trackState) { const enriched = enrichGrassrootsVehicleContext(vc2, profile2); const track = trackState ?? vc2.track_state ?? enriched.track_state; if (profile2 === "quarter_midget" && enriched.chassis_mfr_key === "stanley") { return pickStanleyRsrPhilosophyLine(); } if (profile2 === "quarter_midget" && enriched.chassis_mfr_key === "bullrider") { return pickBullriderPhilosophyLine(); } if (profile2 === "quarter_midget" && enriched.chassis_mfr_key === "nc") { return pickNcPhilosophyLine(); } if (profile2 === "flat_kart") { return pickFlatKartPhilosophyLine(enriched.chassis_mfr_key, track); } if (profile2 === "outlaw_kart" && isOutlawDeepBuilder(enriched.chassis_mfr_key)) { return pickOutlawPhilosophyLine(enriched.chassis_mfr_key, track); } if (profile2 === "micro_sprint_600" && isMicroNonHyperDeepBuilder(enriched.chassis_mfr_key)) { return pickMicroNonHyperPhilosophyLine(enriched.chassis_mfr_key, track); } if (enriched.chassis_mfr_key === "hyper") { return pickHyperPhilosophyLine(profile2); } if (isThinDataChassisCase(profile2, enriched)) { const brief = pickThinDataChassisBrief(profile2, enriched); if (brief) return brief; } const priors = grassrootsChassisManufacturerPriors(profile2, enriched); const first = priors.find((p) => p.lever === "chassis_mfr" || p.lever.includes("philosophy")); if (!first) { return pickThinDataChassisBrief(profile2, enriched) || enriched.chassis_philosophy || null; } return `${first.action} \u2014 ${first.reason.replace(/^public baseline[^:]*:\s*/i, "")}`; } function resolveGrassrootsChassisDeepCaveat(profile2, enrichedVc = {}) { const key = enrichedVc.chassis_mfr_key; if (profile2 === "quarter_midget" && key === "stanley") return STANLEY_RSR_QM_CAVEAT; if (profile2 === "quarter_midget" && key === "bullrider") return BULLRIDER_QM_CAVEAT; if (profile2 === "quarter_midget" && key === "nc") return NC_QM_CAVEAT; if (profile2 === "flat_kart") { return FLAT_KART_CAVEAT; } if (profile2 === "micro_sprint_600" && isMicroNonHyperDeepBuilder(key)) { return MICRO_NON_HYPER_CAVEAT; } if ((profile2 === "micro_sprint_600" || profile2 === "lightning_sprint") && key === "hyper") { return HYPER_RACING_CAVEAT; } if (profile2 === "outlaw_kart" && isOutlawDeepBuilder(key)) { return OUTLAW_KART_DEEP_CAVEAT; } return null; } // scripts/lib/experimental/outlawKartBaselinePriors.mjs function normalizeSurfaceState2(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var OUTLAW_PUBLIC_BASELINE_CAVEAT = "Public baseline / starting point from available manufacturer & community knowledge \u2014 validate with your own data."; var OUTLAW_KART_BASELINE = { source: "Outlaw Kart grassroots baseline (Slack guide + community norms)", cross_pct: "59-66", left_pct: "52-58", rear_stagger_in: { tacky: "1.5-3.5", drying: "1.25-3", slick: "1-2.5", greasy: "2-4", unknown: "1.5-3.5" }, rr_tire: "RR tread duro \u226550 (Hoosier D50 class at KAM-style rules)", offset_note: "Offset cage chassis \u2014 tune cross %, seat cradle, inch stagger; not LO206 rear track-width math" }; function resolveOutlawChassisHint(vc2 = {}, trackState) { return resolveGrassrootsChassisHint("outlaw_kart", vc2, trackState); } var O = OUTLAW_KART_BASELINE; var PRIORS_BY_BUCKET2 = { tacky: [ { lever: "cross", action: `Scale and log cross weight (${O.cross_pct}% typical outlaw band) before chasing stagger`, reason: "public baseline (outlaw): offset cage kart \u2014 cross % is the primary balance tool on dirt oval, not symmetric kart width" }, { lever: "stagger", action: `Log rear inch stagger (${O.rear_stagger_in.tacky} in on 1/8 \u2014 adjust for track size) \u2014 remeasure hot`, reason: "public baseline (outlaw): offset chassis uses stagger + cross together \u2014 high bank may want upper band" }, { lever: "wing", action: "Winged outlaw: small wing angle steps (0.5\xB0) \u2014 wing + cross stack quickly on 1/8 high-bank", reason: "public baseline (outlaw): cage wing moves entry load; balance wing vs straight-line speed on outdoor tracks" }, { lever: "seat", action: "Log seat cradle position (mm) with cross \u2014 seat moves bite and cross on Toigo/Phantom-style offsets", reason: "public baseline (outlaw): scaling/weight distribution starts with seat + cross before tire prep chasing" }, { lever: "tire", action: `Confirm ${O.rr_tire} \u2014 tread, no prep at KAM-style outlaw rules`, reason: "public baseline (outlaw): RR compound/duro is a rules lever \u2014 log duro each session" }, { lever: "geometry", action: "Align toe/caster baseline before cross changes \u2014 Slack guide recommends RF 0 toe, LF 1/16 out as reference", reason: "public baseline (outlaw): geometry first on offset kart \u2014 large front changes need re-align" }, { lever: "discipline", action: "One lever per run \u2014 cross, seat, wing, and stagger interact on offset outlaw karts", reason: "public baseline (outlaw): community norm \u2014 isolate variables so logged runs can replace these priors" } ], drying: [ { lever: "stagger", action: `As track frees, take rear stagger toward ${O.rear_stagger_in.drying} in before wing add`, reason: "public baseline (outlaw): slick transition on offset kart \u2014 stagger down often before more wing" }, { lever: "cross", action: "Re-check cross after stagger change \u2014 offset chassis transfers weight differently as grip falls off", reason: "public baseline (outlaw): Slack scaling principle \u2014 less grip may need less cross built into baseline" }, { lever: "wing", action: "Plan wing step for feature \u2014 drying outdoor tracks often want less wing than heavy qual", reason: "public baseline (outlaw): wing rail fore/aft changes angle and load together on cage karts" }, { lever: "tire", action: "Log RR temp/duro \u2014 tread RR may over-grow on drying tracks before cross fixes push", reason: "public baseline (outlaw): tire management is primary on 2-stroke outlaw programs" } ], slick: [ { lever: "stagger", action: `Slick: reduce rear stagger toward ${O.rear_stagger_in.slick} in (1/8 in steps)`, reason: "public baseline (outlaw): less stagger tightens on offset dirt oval \u2014 one lever per run" }, { lever: "wing", action: "Slick: step wing down for feature if push/off bind \u2014 or add small wing only if loose entry", reason: "public baseline (outlaw): max wing can kill straight speed on light outlaw kart" }, { lever: "cross", action: "Slick: cross may need to come down 0.3-0.5% from tacky baseline on tread RR", reason: "public baseline (outlaw): heavy cross + slick track often binds off on throttle" }, { lever: "seat", action: "If tight center after cross drop, try seat mm tweak before second cross change", reason: "public baseline (outlaw): seat cradle splits entry vs center on offset chassis" } ], greasy: [ { lever: "cross", action: "Heavy/greasy: cross may sit mid-band \u2014 avoid stacking max cross + max wing on first laps", reason: "public baseline (outlaw): mechanical grip is high \u2014 offset kart gets reactive quickly" }, { lever: "stagger", action: `Greasy: rear stagger toward ${O.rear_stagger_in.greasy} in upper band if loose`, reason: "public baseline (outlaw): more stagger can help loosen on heavy moisture before shock/tire prep" }, { lever: "wing", action: "Heavy track: start lower wing \u2014 add angle only if loose entry after cross baseline", reason: "public baseline (outlaw): wing + cross stack on cage outlaw \u2014 start conservative" } ], unknown: [ { lever: "track_state", action: "Log tacky \u2192 drying \u2192 slick \u2014 outlaw priors stratify by surface like sprint/midget paths", reason: "public baseline (outlaw): offset winged kart setup is condition-specific" }, { lever: "cross", action: `Establish cross baseline (${O.cross_pct}%) and left ${O.left_pct}% on scales before hot laps`, reason: "public baseline (outlaw): scaling principles from Slack public guide \u2014 adjust for driver weight" }, { lever: "stagger", action: `Rear stagger starting band ${O.rear_stagger_in.unknown} in \u2014 ${O.offset_note}`, reason: "public baseline (outlaw): not quarter midget or full sprint \u2014 outlaw has its own tool stack" }, { lever: "chassis", action: "Log chassis manufacturer + model in Garage (Setup \u2192 Chassis) for sharper priors over time", reason: "public baseline (outlaw): QRC, Phantom, Toigo, Slack, Ultramax differ \u2014 routing improves with chassis on file" }, { lever: "discipline", action: "One DOF per run \u2014 confirm with A-B-A before feature", reason: "public baseline (outlaw): community + manufacturer guidance is starting point only" } ] }; function outlawKartBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState2(trackState); let list = (PRIORS_BY_BUCKET2[bucket] || PRIORS_BY_BUCKET2.unknown).filter((p) => !skip.has(p.lever)); const hint = resolveOutlawChassisHint(opts.vehicleContext || {}, trackState); if (hint && !skip.has("chassis_mfr")) { list = [{ lever: "chassis_mfr", action: hint.split(" \u2014 ")[0], reason: `public baseline (outlaw): ${hint.includes(" \u2014 ") ? hint.split(" \u2014 ").slice(1).join(" \u2014 ") : hint}` }, ...list.filter((p) => p.lever !== "chassis")]; } else if (opts.vehicleContext?.chassis_model_missing && !skip.has("chassis")) { const generic = outlawGenericWingedPriors(trackState, { maxPriors: 6 }).filter((p) => !skip.has(p.lever)).map((p) => ({ lever: p.lever, action: p.action, reason: p.reason })); const hasChassisPrior = list.some((p) => p.lever === "chassis"); if (!hasChassisPrior) { list.unshift({ lever: "chassis", action: "Add chassis manufacturer + model in Garage \u2192 Setup \u2192 Chassis for routed outlaw advice", reason: "public baseline (outlaw): QRC, Phantom, Toigo, Slack, Ultramax routing needs chassis on file" }); } list = [...generic, ...list.filter((p) => !generic.some((g) => g.lever === p.lever))]; } return list; } // scripts/lib/experimental/quarterMidgetBaselinePriors.mjs function normalizeSurfaceState3(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var QM_PUBLIC_BASELINE_CAVEAT = "Public baseline / starting point from manufacturer & community knowledge \u2014 validate with your own data and your chassis manufacturer's setup sheet."; var QUARTER_MIDGET_BASELINE = { source: "Quarter Midget grassroots baseline (Stanley/RSR public + community norms)", tire_psi: { rf: "10-11", rr: "10-11", lf: "5-6", lr: "5-6" }, ride_height_in: "~1.5 no driver (cross tube ~2.25 in on Stanley/RSR reference)", cross_pct: "52-54", left_pct: "55-58", rear_pct: "55-58", stagger_note: "QMA spec tire \u2014 follow manufacturer sheet before inch stagger chasing" }; function resolveQuarterMidgetChassisHint(vc2 = {}) { return resolveGrassrootsChassisHint("quarter_midget", vc2); } var Q = QUARTER_MIDGET_BASELINE; var PSI4 = Q.tire_psi; var PRIORS_BY_BUCKET3 = { tacky: [ { lever: "manufacturer_baseline", action: "Start from YOUR chassis manufacturer baseline sheet before any changes (Bullrider, Stanley, NC each differ)", reason: "public baseline (QM): highest-value community advice \u2014 brand-specific squaring, panhard, and birdcage points are not interchangeable" }, { lever: "squaring", action: "Square axle and birdcages to manufacturer process \u2014 1/16 in error shows up on small tracks", reason: "public baseline (QM): Stanley/RSR public guide emphasizes squaring before ride height and scaling" }, { lever: "tire_pressure", action: `Log starting pressures: RF ${PSI4.rf} / RR ${PSI4.rr} / LF ${PSI4.lf} / LR ${PSI4.lr} psi (Stanley/RSR public reference band)`, reason: "public baseline (QM): right-side higher than left is normal QM baseline \u2014 adjust per track, not sprint/midget bands" }, { lever: "ride_height", action: `Set ride height baseline ${Q.ride_height_in} \u2014 always measure the same way (with or without driver)`, reason: "public baseline (QM): inconsistent driver-in/out measurement breaks comparisons with other handlers" }, { lever: "scaling", action: `Scale baseline: cross ~${Q.cross_pct}%, left ~${Q.left_pct}%, rear ~${Q.rear_pct}% (no driver on Stanley/RSR reference)`, reason: "public baseline (QM): use X-method for cross \u2014 ballast moves left/rear; shock collars change cross only" }, { lever: "track_width", action: "Narrow track width to manufacturer baseline before scaling (Stanley/RSR: minimal front spacer, tight LR/RR spacing)", reason: "public baseline (QM): track width is part of baseline on many QM sheets \u2014 log before changing cross" }, { lever: "discipline", action: "One lever per run \u2014 recheck ride height after ballast or cross changes", reason: "public baseline (QM): Stanley guide rechecks ride height after scaling before trusting cross" } ], drying: [ { lever: "tire_pressure", action: "Transition psi down slightly as track frees \u2014 1 psi steps on RF/RR first", reason: "public baseline (QM): drying quarter midget tracks often want less right-side psi before geometry changes" }, { lever: "cross", action: "Re-check cross after stagger or psi change \u2014 offset QM chassis transfers weight differently as grip falls", reason: "public baseline (QM): scaling principles still apply \u2014 less grip may need less cross built in" }, { lever: "ride_height", action: "If push appears while drying, small ride-height tweak per manufacturer sheet before camber chase", reason: "public baseline (QM): ride height bands differ by brand \u2014 do not copy full midget numbers" } ], slick: [ { lever: "tire_pressure", action: "Slick: ease RF/RR toward lower band \u2014 protect left-side psi from over-drop (affects stagger effective)", reason: "public baseline (QM): community asphalt/dirt slick bands vary \u2014 log hot pyrometer if available" }, { lever: "cross", action: "Slick: cross may need to come down 0.5-1% from tacky baseline \u2014 X-method only", reason: "public baseline (QM): heavy cross on slick often binds on corner exit for light QM cars" }, { lever: "scaling", action: "Re-scale left/rear % after cross change \u2014 ballast not shock collars for left/rear targets", reason: "public baseline (QM): Stanley/RSR public guide \u2014 left/rear % moves with ballast only" } ], greasy: [ { lever: "tire_pressure", action: "Heavy/greasy: may run upper psi band on right side \u2014 still 1 psi per change", reason: "public baseline (QM): more moisture often wants more RS psi before geometry" }, { lever: "cross", action: "Avoid max cross + max camber on first laps \u2014 QM gets reactive on heavy tracks", reason: "public baseline (QM): start conservative on greasy \u2014 manufacturer baseline first" } ], unknown: [ { lever: "track_state", action: "Log tacky \u2192 drying \u2192 slick \u2014 QM priors stratify by surface like other classes", reason: "public baseline (QM): manufacturer sheets are condition-specific on pavement and dirt" }, { lever: "manufacturer_baseline", action: "Download/use your chassis brand baseline (Bullrider, Stanley/RSR, NC) before custom tuning", reason: "public baseline (QM): each major QM manufacturer uses different measurement references" }, { lever: "chassis", action: "Log chassis manufacturer + model in Garage \u2192 Setup \u2192 Chassis for routed QM advice", reason: "public baseline (QM): Bullrider vs Stanley vs NC squaring and panhard points differ" }, { lever: "tire_pressure", action: `Reference band: RF ${PSI4.rf} / RR ${PSI4.rr} / LF ${PSI4.lf} / LR ${PSI4.lr} psi (Stanley/RSR public starting point)`, reason: "public baseline (QM): validate against your manufacturer sheet \u2014 not full midget or sprint numbers" }, { lever: "discipline", action: "One DOF per run \u2014 confirm with A-B-A before feature", reason: "public baseline (QM): community + manufacturer guidance is starting point only" } ] }; function quarterMidgetBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState3(trackState); let list = (PRIORS_BY_BUCKET3[bucket] || PRIORS_BY_BUCKET3.unknown).filter((p) => !skip.has(p.lever)); const hint = resolveQuarterMidgetChassisHint(opts.vehicleContext || {}); if (hint && !skip.has("chassis_mfr")) { list = [{ lever: "chassis_mfr", action: hint.split(" \u2014 ")[0], reason: `public baseline (QM): ${hint.includes(" \u2014 ") ? hint.split(" \u2014 ").slice(1).join(" \u2014 ") : hint}` }, ...list.filter((p) => p.lever !== "chassis" && p.lever !== "manufacturer_baseline")]; } else if (opts.vehicleContext?.chassis_model_missing && !skip.has("chassis")) { const hasChassisPrior = list.some((p) => p.lever === "chassis"); if (!hasChassisPrior) { list.unshift({ lever: "chassis", action: "Add chassis manufacturer (Bullrider, Stanley, NC, etc.) in Garage \u2192 Setup \u2192 Chassis", reason: "public baseline (QM): manufacturer-specific baseline sheet should drive first changes" }); } } return list; } // scripts/lib/experimental/microSprint600BaselinePriors.mjs function normalizeSurfaceState4(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } function resolveMicroSprintChassisHint(vc2 = {}, trackState) { return resolveGrassrootsChassisHint("micro_sprint_600", vc2, trackState); } var MICRO_SPRINT_PUBLIC_BASELINE_CAVEAT = "Public baseline / starting point from available manufacturer & community knowledge (primarily Hyper Racing) \u2014 validate with your own data."; var MICRO_SPRINT_600_BASELINE = { source: "Hyper Racing 600cc Micro Sprint setup guides (public)", tire_psi: { lf: "9", rf: "9", lr: "5-8", rr: "6.5-10" }, stagger_rear_in: { tacky: "5-8.5", drying: "4.5-7", slick: "4-7", greasy: "5-9", unknown: "5-8.5" }, block_height_in: "1-1/2 all corners (Hyper X4-X7 wishbone normal winged reference)", torsion_bars: { lf: ".675", rf: ".675", lr: ".725", rr: ".750 (+1 turn RR typical)" }, ride_height_note: "Block/torsion turns set ride height \u2014 measure consistently; Hyper manual has bar vs coil references", shock_note: "Start from Hyper manual valving for your suspension type (torsion vs coil vs wishbone)" }; function isMicroSprint600Profile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); if (/lightning/.test(c)) return false; if (/jr\.?\s*sprint|junior sprint/.test(c) || ct === "jrsprint") return false; if (/turf/.test(c) || ct === "microturf") return false; if (/\b270\b|\b250\b/.test(c) && /micro|sprint/.test(c)) return false; if (ct === "micro270") return false; if (/mini midget|micro midget/.test(c) && !/micro sprint/.test(c)) return false; if (ct === "micro" || ct === "microsprint600") return true; if (/600 micro|restricted micro|now600/.test(c)) return true; if (/\bmicro sprint\b/.test(c) && !/\b270\b|\b250\b|turf|jr\.?\s*sprint|junior/.test(c)) return true; if (/\bmicro\b/.test(c) && /600|restricted|now600/.test(c)) return true; return false; } var M2 = MICRO_SPRINT_600_BASELINE; var PSI5 = M2.tire_psi; var STG = M2.stagger_rear_in; var PRIORS_BY_BUCKET4 = { tacky: [ { lever: "squaring", action: "Square axle and birdcages to Hyper squaring procedure before ride height or scaling", reason: "public baseline (600 micro): Hyper emphasizes repeatable baseline \u2014 1/16 in axle error shows on 1/6\u20131/8 tracks" }, { lever: "baseline", action: "Establish repeatable baseline sheet (blocks, bars, wing, stagger) before chasing one-lap fixes", reason: "public baseline (600 micro): Hyper setup manuals are designed as return-to baseline when the car loses the handle" }, { lever: "tire_pressure", action: `Log starting pressures: LF ${PSI5.lf} / RF ${PSI5.rf} / LR ${PSI5.lr} / RR ${PSI5.rr} psi (Hyper normal winged band)`, reason: "public baseline (600 micro): right-side stiffer loosens, left-side stiffer tightens \u2014 LR psi also affects effective stagger" }, { lever: "stagger", action: `Start rear stagger ${STG.tacky} in \u2014 remeasure hot after laps 8-10 (1/4 in steps)`, reason: "public baseline (600 micro): Hyper X4-X7 normal track \u2014 more stagger loosens, less stagger tightens" }, { lever: "blocks", action: `Set block baseline ${M2.block_height_in} \u2014 then tune with torsion/coil turns per Hyper manual`, reason: "public baseline (600 micro): ride height on micro comes from blocks + bar/coil turns \u2014 seat height changes block targets" }, { lever: "torsion", action: `Torsion starting band (Hyper wishbone ref): LF ${M2.torsion_bars.lf} / RF ${M2.torsion_bars.rf} / LR ${M2.torsion_bars.lr} / RR ${M2.torsion_bars.rr}`, reason: "public baseline (600 micro): bar size and arm length are chassis-specific \u2014 verify your year/suspension guide" }, { lever: "shock", action: M2.shock_note, reason: "public baseline (600 micro): Hyper lists rebound/comp starting points per corner \u2014 one click at a time after baseline" }, { lever: "discipline", action: "One lever per run \u2014 Hyper tighten/loosen lists are ordered; do not stack wing + stagger + psi same heat", reason: "public baseline (600 micro): isolate variables so logged A-B-A replaces these priors quickly" } ], drying: [ { lever: "stagger", action: `As track frees, take 1/4 in stagger out (toward ${STG.drying} in low end) before shock chasing`, reason: "public baseline (600 micro): Hyper drying sequence \u2014 less stagger first as rubber builds" }, { lever: "rr", action: "Ease RR pressure down as track slicks (1 psi steps toward lower RR band)", reason: "public baseline (600 micro): lower RR psi can tighten \u2014 protect RR for drive as track frees" }, { lever: "wing", action: "If tight while drying, move wing back 1 hole before bar changes (Hyper normal 1/6\u20131/8 winged)", reason: "public baseline (600 micro): wing position is primary aero balance on micro \u2014 small steps only" }, { lever: "ride_height", action: "Entry push while drying: +1 turn all four corners (blocks/bars) for forward bite before panhard chase", reason: "public baseline (600 micro): Hyper \u2014 raising ride height tightens mid/exit on smaller tracks" } ], slick: [ { lever: "stagger", action: `To tighten on slick: reduce stagger toward ${STG.slick} in (64 LR tire or smaller LR roll-out)`, reason: "public baseline (600 micro): Hyper tighten list \u2014 less stagger first on slick" }, { lever: "rr", action: "To tighten: lower RR psi and/or move RR in \u2014 do not stack with stagger same run", reason: "public baseline (600 micro): RR in + lower RR psi are primary slick tighten moves" }, { lever: "wing", action: "Slick tight: wing back; slick loose: wing front (keep angle ~28\xB0 small track per Hyper)", reason: "public baseline (600 micro): wing fore/aft shifts load entry-to-exit on winged 600" }, { lever: "torsion", action: "Loosen on slick: soften front bars / stiffen rear bars (Hyper order) \u2014 1/2 turn steps", reason: "public baseline (600 micro): bar split changes cross feel without stacking shock moves" }, { lever: "shock", action: "Loosen entry: stiffer RF rebound; tighten entry: softer LF rebound \u2014 one corner at a time", reason: "public baseline (600 micro): Hyper lists RF/LF rebound as entry balance levers on torsion cars" } ], greasy: [ { lever: "stagger", action: `Heavy/greasy: may run upper stagger band (${STG.greasy} in) \u2014 1/4 in steps from baseline`, reason: "public baseline (600 micro): more stagger loosens \u2014 wet often wants wider band before shock work" }, { lever: "tire_pressure", action: "Greasy: run upper psi band on right side \u2014 still 1 psi per change", reason: "public baseline (600 micro): Hyper \u2014 tacky/wet usually carries more psi than slick" }, { lever: "cross", action: "Scale with driver in car, shocks unhooked on torsion \u2014 log left/rear % before cross chase", reason: "public baseline (600 micro): Hyper scaling optional but useful \u2014 consistent pad location each week" } ], unknown: [ { lever: "track_state", action: "Log tacky \u2192 drying \u2192 slick each session \u2014 Hyper guidance stratifies by grip", reason: "public baseline (600 micro): tire psi and stagger bands shift with surface state" }, { lever: "squaring", action: "Square to manufacturer/Hyper procedure before any baseline numbers", reason: "public baseline (600 micro): axle squareness and birdcage timing mask real setup learning" }, { lever: "tire_pressure", action: `Reference band: LF ${PSI5.lf} / RF ${PSI5.rf} / LR ${PSI5.lr} / RR ${PSI5.rr} psi (Hyper 600 winged normal)`, reason: "public baseline (600 micro): not quarter midget or full midget psi bands \u2014 micro-specific" }, { lever: "stagger", action: `Rear stagger start ${STG.unknown} in \u2014 measure hot; 270cc classes often run lower band`, reason: "public baseline (600 micro): Hyper publishes separate 270 vs 600 guides" }, { lever: "chassis", action: "Log chassis manufacturer + model in Garage \u2192 Setup \u2192 Chassis for routed micro advice", reason: "public baseline (600 micro): Hyper bar lengths and rack heights differ by model year" }, { lever: "discipline", action: "One DOF per run \u2014 confirm with A-B-A before feature", reason: "public baseline (600 micro): community + Hyper guidance is starting point only" } ] }; function microSprint600BaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState4(trackState); let list = (PRIORS_BY_BUCKET4[bucket] || PRIORS_BY_BUCKET4.unknown).filter((p) => !skip.has(p.lever)); const hint = resolveMicroSprintChassisHint(opts.vehicleContext || {}, trackState); if (hint && !skip.has("chassis_mfr")) { list = [{ lever: "chassis_mfr", action: hint.split(" \u2014 ")[0], reason: `public baseline (600 micro): ${hint.includes(" \u2014 ") ? hint.split(" \u2014 ").slice(1).join(" \u2014 ") : hint}` }, ...list.filter((p) => p.lever !== "chassis" && p.lever !== "baseline")]; } else if (opts.vehicleContext?.chassis_model_missing && !skip.has("chassis")) { const hasChassisPrior = list.some((p) => p.lever === "chassis"); if (!hasChassisPrior) { list.unshift({ lever: "chassis", action: "Add chassis mfr + model (Hyper, Emmick, Spike, etc.) in Garage \u2192 Setup \u2192 Chassis", reason: "public baseline (600 micro): Hyper publishes model-specific guides \u2014 other builders have less public detail" }); } } return list; } // scripts/lib/experimental/juniorSprintBaselinePriors.mjs function normalizeSurfaceState5(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var JUNIOR_SPRINT_PUBLIC_BASELINE_CAVEAT = 'Public baseline from Hyper Jr Sprint guides and 8" tire platform norms \u2014 validate on your car. Not 600cc micro or full midget setup.'; var JUNIOR_SPRINT_BASELINE = { source: "Hyper Racing Junior Sprint + Briggs World Formula platform", engine: "204cc Briggs World Formula (sealed)", tire_diameter: '8"', tire_psi: { lf: "7-9", rf: "7-9", lr: "4-6", rr: "5-7" }, stagger_rear_in: { tacky: "2-3.5", drying: "2-3", slick: "2-2.75", greasy: "2.5-3.5", unknown: "2-4" }, wing_note: "Scaled junior wing \u2014 0.25\xB0 steps; smaller angle range than 600 micro", shock_note: "Smaller shock bodies \u2014 lighter rebound/comp than 600 micro; one click per run", torsion_bars: { note: "Jr chassis bar sizes differ from 600 \u2014 verify Hyper Jr sheet for your year" } }; var J = JUNIOR_SPRINT_BASELINE; var STG2 = J.stagger_rear_in; var PRIORS_BY_BUCKET5 = { tacky: [ { lever: "discipline", action: 'Confirm class at registration \u2014 Jr Sprint is 204cc / 8" tires, not 600 micro or QM', reason: "public baseline (jr sprint): wrong class sheet is the #1 parent mistake on combined grids" }, { lever: "tire_pressure", action: `Log cold psi: LF ${J.tire_psi.lf} / RF ${J.tire_psi.rf} / LR ${J.tire_psi.lr} / RR ${J.tire_psi.rr} \u2014 0.25\u20130.5 psi steps only on 8" tires`, reason: 'public baseline (jr sprint): 8" tires heat faster than 10" micro \u2014 smaller psi steps' }, { lever: "stagger", action: `Rear stagger ${STG2.tacky} in cold \u2014 remeasure hot after laps 6\u20138 (1/8 mi tracks)`, reason: 'public baseline (jr sprint): lower stagger band than 600 micro \u2014 do not copy 4\u20136" micro sheet' }, { lever: "wing", action: J.wing_note, reason: "public baseline (jr sprint): wing angle primary on winged jr programs \u2014 bar/torsion secondary" }, { lever: "shock", action: J.shock_note, reason: "public baseline (jr sprint): shock valving scaled for lighter car \u2014 not 600 micro chart" }, { lever: "one_change", action: 'One lever per run: psi OR 1/8" stagger OR 0.25\xB0 wing \u2014 never stack like full sprint', reason: "public baseline (jr sprint): light car responds to small steps" } ], drying: [ { lever: "stagger", action: `Take 1/8" stagger out toward ${STG2.drying} in as track rubbers`, reason: "public baseline (jr sprint): growth tightens off \u2014 remeasure hot before wing chase" }, { lever: "rr", action: 'Ease RR psi 0.25\u20130.5 as track frees \u2014 protect 8" RR for drive', reason: "public baseline (jr sprint): RR overheats faster than 600 on drying line" }, { lever: "wing", action: "If tight while drying: wing down 0.25\xB0 before bar change", reason: "public baseline (jr sprint): smaller wing steps than adult micro" } ], slick: [ { lever: "stagger", action: `Slick: stagger toward ${STG2.slick} in \u2014 1/8" steps max`, reason: "public baseline (jr sprint): less growth than 600 \u2014 quarter-inch micro steps are too big" }, { lever: "wing", action: "Feature slick: wing down 0.25\u20130.5\xB0 from qual if growth tightens off", reason: "public baseline (jr sprint): do not copy adult 600 0.5\xB0+ wing stacks" }, { lever: "lr_stop", action: "LR stop last after psi/stagger/wing \u2014 jr car rarely needs sprint-style LR stop amounts", reason: "public baseline (jr sprint): not full midget or 410 vocabulary" } ], greasy: [ { lever: "tire_pressure", action: "Moisture: psi up slightly all corners \u2014 patience until line packs", reason: 'public baseline (jr sprint): 8" tires need grip before stagger chase' }, { lever: "stagger", action: `Moisture stagger band ${STG2.greasy} in \u2014 verify rulebook max`, reason: "public baseline (jr sprint): class stagger limits lower than micro 600" } ], unknown: [ { lever: "baseline", action: "Square and scale Jr chassis per Hyper Jr procedure before any tuning", reason: "public baseline (jr sprint): repeatable baseline beats pit-row folklore" }, { lever: "stagger", action: `Start rear stagger ${STG2.unknown} in \u2014 log hot every session`, reason: "public baseline (jr sprint): anchor Hyper Jr roll-out, not 600 manual" } ] }; function juniorSprintBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState5(trackState); const list = PRIORS_BY_BUCKET5[bucket] || PRIORS_BY_BUCKET5.unknown; return list.filter((p) => !skip.has(p.lever)); } function isJuniorSprintProfile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); return ct === "jrsprint" || /jr\.?\s*sprint|junior sprint/.test(c); } // scripts/lib/experimental/micro270BaselinePriors.mjs function normalizeSurfaceState6(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var MICRO_270_PUBLIC_BASELINE_CAVEAT = "Public baseline from Hyper 270 Micro guides \u2014 250/270cc platform is NOT 600cc micro. Validate tire roll-out and shock valving on your chassis."; var MICRO_270_BASELINE = { source: "Hyper Racing 270 Micro Sprint setup guides (public)", engine: "250\u2013270cc motorcycle engine (verify 250 vs 270 rulebook)", tire_diameter: '10" (smaller circumference than 600 roll-out)', tire_psi: { lf: "8-10", rf: "8-10", lr: "5-7", rr: "6-8" }, stagger_rear_in: { tacky: "3.5-6", drying: "3-5", slick: "3-4.5", greasy: "4-6", unknown: "3-6" }, wing_note: "270 wing chord/angle range smaller than open 600 \u2014 0.25\u20130.5\xB0 steps on 1/8\u20131/4 mi", shock_note: "Lighter valving than 600 micro \u2014 Hyper 270 shock sheet; do not copy 600 rebound/comp", weight_note: "650\u2013800 lb typical \u2014 less LR stop authority than 600" }; var M3 = MICRO_270_BASELINE; var STG3 = M3.stagger_rear_in; var PRIORS_BY_BUCKET6 = { tacky: [ { lever: "discipline", action: "270/250 micro \u2260 600 micro \u2014 verify engine displacement and tire roll-out at tech before baseline", reason: "public baseline (270 micro): combined grids cause wrong shock/wing/stagger copy from 600 pit row" }, { lever: "tire_pressure", action: `Cold psi band: LF ${M3.tire_psi.lf} / RF ${M3.tire_psi.rf} / LR ${M3.tire_psi.lr} / RR ${M3.tire_psi.rr}`, reason: 'public baseline (270 micro): 10" tires but lighter car than 600 \u2014 psi before wing' }, { lever: "stagger", action: `Rear stagger ${STG3.tacky} in \u2014 Hyper 270 roll-out chart, not 600 manual`, reason: "public baseline (270 micro): stagger band sits between jr sprint and 600" }, { lever: "shock", action: M3.shock_note, reason: "public baseline (270 micro): shock package sized for 270 power/weight \u2014 one click A-B-A" }, { lever: "wing", action: M3.wing_note, reason: "public baseline (270 micro): wing + J-ladder like scaled sprint \u2014 smaller steps than 600" } ], drying: [ { lever: "stagger", action: `Drying: 1/4" stagger out toward ${STG3.drying} in before second wing move`, reason: "public baseline (270 micro): growth moderate \u2014 remeasure hot lap 8\u201310" }, { lever: "compound", action: "D10\u2192D12 step as moisture leaves \u2014 270 heats tires quickly on abrasive 1/8 mi", reason: "public baseline (270 micro): compound before LR stop on slick transition" } ], slick: [ { lever: "stagger", action: `Slick: ${STG3.slick} in band \u2014 quarter-inch steps`, reason: 'public baseline (270 micro): not 600 4\u20136" default on same track' }, { lever: "wing", action: "Tight off from growth: wing down 0.25\u20130.5\xB0 after hot stagger check", reason: "public baseline (270 micro): lighter car wing-sensitive through long runs" } ], greasy: [ { lever: "tire_pressure", action: "Wet/tacky: psi up 0.3\u20130.5 all corners \u2014 D10 primary if legal", reason: "public baseline (270 micro): moisture band similar to 600 but lighter weight transfer" } ], unknown: [ { lever: "baseline", action: "Square axle and log J-ladder + panhard refs (Focused PIVOT) before rate chasing", reason: "public baseline (270 micro): linkage baseline beats generic 600 hole chart" }, { lever: "stagger", action: `Start ${STG3.unknown} in rear \u2014 verify 250 vs 270 local rulebook`, reason: "public baseline (270 micro): regional 250 may run lower stagger max" } ] }; function micro270BaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState6(trackState); const list = PRIORS_BY_BUCKET6[bucket] || PRIORS_BY_BUCKET6.unknown; return list.filter((p) => !skip.has(p.lever)); } function isMicro270Profile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); if (/600|now600|restricted micro/.test(c) && !/\b270\b|\b250\b/.test(c)) return false; if (/jr\.?\s*sprint|junior sprint/.test(c) || ct === "jrsprint") return false; if (/turf/.test(c) || ct === "microturf") return false; if (ct === "micro270") return true; if (/\b270\b/.test(c) && /micro|sprint/.test(c)) return true; if (/\b250\b/.test(c) && /micro|sprint/.test(c)) return true; return false; } // scripts/lib/experimental/modLiteBaselinePriors.mjs function normalizeSurfaceState7(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var MOD_LITE_PUBLIC_BASELINE_CAVEAT = "Public baseline for Mod Lite / mini-modified \u2014 lighter than A-mod, different tires/shocks/stagger than 600 micro. Verify POWRi/regional rulebook."; var MOD_LITE_BASELINE = { source: "Mod Lite regional norms + mini-mod setup practice", engine: "600cc or spec limited motor", tire_note: "Mod lite spec tire \u2014 smaller than full modified", tire_psi: { lr: "6-9", rr: "7-10", lf: "8-11", rf: "8-11" }, stagger_rear_in: { tacky: "1.5-3", drying: "1.25-2.5", slick: "1-2", unknown: "1.25-2.75" }, cross_note: "Cross range smaller than A-mod \u2014 re-scale left-side % after every wedge move", shock_note: "Coilover or leaf per builder \u2014 PSI and stagger before large LR ballast", weight_lb: "1,000\u20131,200 with driver" }; var M4 = MOD_LITE_BASELINE; var PRIORS_BY_BUCKET7 = { tacky: [ { lever: "discipline", action: "Mod Lite \u2260 600 micro \u2260 A-mod \u2014 verify engine, tire, and body rules before copying pit-row setup", reason: "public baseline (mod lite): combined events with micros cause wrong tire/stagger copy" }, { lever: "tire_pressure", action: `PSI all four: LR ${M4.tire_psi.lr} / RR ${M4.tire_psi.rr} / LF ${M4.tire_psi.lf} / RF ${M4.tire_psi.rf} cold`, reason: "public baseline (mod lite): light car \u2014 psi primary on weekly bullrings" }, { lever: "stagger", action: `Rear stagger ${M4.stagger_rear_in.tacky} in \u2014 lower band than A-mod`, reason: "public baseline (mod lite): stagger over rulebook max fails tech" }, { lever: "cross", action: M4.cross_note, reason: "public baseline (mod lite): left-side % moves fast on ~1,100 lb car" }, { lever: "lr", action: "LR ballast +10\u201315# max per stop after re-scale \u2014 not A-mod +25# amounts", reason: "public baseline (mod lite): large LR pushes left-side illegal quickly" } ], slick: [ { lever: "stagger", action: `Slick: stagger toward ${M4.stagger_rear_in.slick} in \u2014 panhard/j-bar before second cross`, reason: "public baseline (mod lite): mechanical grip before LR on slick" }, { lever: "cross", action: "Cross down 0.2% if push entry on rubbered slick \u2014 re-scale immediately", reason: "public baseline (mod lite): half A-mod cross steps only" } ], unknown: [ { lever: "baseline", action: "Scale cold with driver \u2014 log left-side % before any ballast", reason: "public baseline (mod lite): weight and left-side are tech checkpoints" }, { lever: "shock", action: M4.shock_note, reason: "public baseline (mod lite): limited suspension vs A-mod \u2014 verify coilover legality" } ] }; function modLiteBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState7(trackState); const list = PRIORS_BY_BUCKET7[bucket] || PRIORS_BY_BUCKET7.unknown; return list.filter((p) => !skip.has(p.lever)); } function isModLiteProfile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); return ct === "modlite" || /mod lite|modlite|600 mini mod|mini modified|powri mod lite/.test(c); } // scripts/lib/experimental/microTurfBaselinePriors.mjs function normalizeSurfaceState8(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var MICRO_TURF_PUBLIC_BASELINE_CAVEAT = "Turf tire micro baseline \u2014 high-grip turf compound and psi drive setup. Not dirt Hoosier D-series or inch-stagger-first workflow."; var MICRO_TURF_BASELINE = { source: "Turf micro regional practice (indoor/outdoor turf tracks)", tire_note: "Turf/compound spec tire \u2014 verify venue compound rules", tire_psi: { note: "Turf often lower psi window than dirt \u2014 0.25 psi steps; hot check every heat" }, stagger_note: "Inch stagger secondary on turf \u2014 compound and psi primary", wing_note: "Many turf programs limit wing \u2014 verify venue; body lean affects grip on low-compliance surface", shock_note: "Softer platform common \u2014 stiff primary shock feels like push on high-grip turf" }; var M5 = MICRO_TURF_BASELINE; var PRIORS_BY_BUCKET8 = { tacky: [ { lever: "discipline", action: "Turf micro \u2260 dirt 600/270 \u2014 leave dirt D12/D15 ladder in the trailer; run turf compound per venue", reason: "public baseline (turf micro): wrong tire family is weekly DQ or no-grip" }, { lever: "tire_pressure", action: M5.tire_psi.note, reason: "public baseline (turf micro): high grip \u2014 overheating shows as tight off late in run" }, { lever: "compound", action: "Ask venue which turf compound for current grip \u2014 swap before stagger or wing chase", reason: "public baseline (turf micro): compound is the sprint stagger equivalent on turf" }, { lever: "shock", action: M5.shock_note, reason: "public baseline (turf micro): LR stop vocabulary rarely applies \u2014 platform softness first" } ], slick: [ { lever: "tire_pressure", action: "If tight off on long turf run: drop RR 0.25 psi after hot check \u2014 not LR stop", reason: "public baseline (turf micro): heat buildup on high-grip surface" }, { lever: "wing", action: M5.wing_note, reason: "public baseline (turf micro): limited aero \u2014 small body/wing changes only if rules allow" } ], unknown: [ { lever: "baseline", action: "Log turf compound + cold/hot psi before any change \u2014 dirt setup sheets do not transfer", reason: "public baseline (turf micro): treat as separate platform from dirt micro" }, { lever: "stagger", action: M5.stagger_note, reason: "public baseline (turf micro): verify if rear split is even legal at venue" } ] }; function microTurfBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState8(trackState); const list = PRIORS_BY_BUCKET8[bucket] || PRIORS_BY_BUCKET8.unknown; return list.filter((p) => !skip.has(p.lever)); } function isMicroTurfProfile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); return ct === "microturf" || /turf.*micro|micro.*turf|turf tire micro|indoor micro|turf sprint/.test(c); } // scripts/lib/experimental/sprintSportsmanBaselinePriors.mjs function normalizeSurfaceState9(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var SPRINT_SPORTSMAN_PUBLIC_BASELINE_CAVEAT = "ASCS Sportsman / 360 Sportsman baseline \u2014 winged 360 platform with restricted/sportsman engine rules. Not crate late model or open 410 tuning."; var SPRINT_SPORTSMAN_BASELINE = { source: "ASCS Sportsman + regional 360 sportsman norms", engine: "360ci sportsman/restricted rules \u2014 verify fuel and claim book", tire_psi: { note: "D12 tacky / D15 transition / D25 slick \u2014 same ladder as 360 winged" }, stagger_rear_in: { tacky: "7-9", drying: "6.5-8.5", slick: "6-8", unknown: "7-10" }, wing_note: "Less power than 410 \u2014 wing drag hurts more on slick; plan feature wing reduction", bar_note: "Torsion bars: 360 sportsman bands \u2014 not 410 rates; LR stop after wing/stagger on slick" }; var S2 = SPRINT_SPORTSMAN_BASELINE; var STG4 = S2.stagger_rear_in; var PRIORS_BY_BUCKET9 = { tacky: [ { lever: "discipline", action: "ASCS Sportsman is winged 360 \u2014 not 602 crate LM, not non-wing 360 on winged program", reason: "public baseline (360 sportsman): registration class sets wing and tire ladder" }, { lever: "compound", action: S2.tire_psi.note, reason: "public baseline (360 sportsman): D12 tacky baseline \u2014 less RR heat than 410 on same compound" }, { lever: "stagger", action: `Rear stagger ${STG4.tacky} in \u2014 sprint band, not late model 1\u20132"`, reason: 'public baseline (360 sportsman): verify 7\u201310" rulebook vs LM stagger copy mistake' }, { lever: "wing", action: S2.wing_note, reason: "public baseline (360 sportsman): wing primary on entry \u2014 log angle with wind each session" }, { lever: "bar", action: S2.bar_note, reason: "public baseline (360 sportsman): conservative bar steps vs open 410 pit row" } ], slick: [ { lever: "wing", action: "Slick feature: wing down 0.5\u20131\xB0 before second LR stop \u2014 sportsman power loads less RR than 410", reason: "public baseline (360 sportsman): wind + slick wing drag common on ASCS-style tracks" }, { lever: "stagger", action: `Hot stagger remeasure lap 8\u201310 \u2014 feature band ${STG4.slick} in`, reason: "public baseline (360 sportsman): growth still sprint-scale \u2014 not LM cross vocabulary" } ], unknown: [ { lever: "baseline", action: "Log wing angle + cold stagger + bar sizes before qual \u2014 sportsman grids combine with open 360 some nights", reason: "public baseline (360 sportsman): verify which rulebook applies on combined events" } ] }; function sprintSportsmanBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState9(trackState); const list = PRIORS_BY_BUCKET9[bucket] || PRIORS_BY_BUCKET9.unknown; return list.filter((p) => !skip.has(p.lever)); } function isSprintSportsmanProfile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); if (/602|604|crate late|late model/.test(c)) return false; return /ascs sportsman|360 sportsman|sportsman 360|sportsman sprint|regional sportsman.*360/.test(c); } // scripts/lib/experimental/trackEvolutionSignals.mjs function num2(v) { const n = parseFloat(v); return Number.isFinite(n) ? n : null; } function sessionRows(sessionLog = []) { const rows = (Array.isArray(sessionLog) ? sessionLog : []).filter((r) => r && r.source !== "track_snap"); if (!rows.length) return rows; if (rows.some((r) => r.ts)) { return rows.slice().sort((a, b) => new Date(a.ts || 0).getTime() - new Date(b.ts || 0).getTime()); } return rows.slice().reverse(); } function series(rows, key) { return rows.map((r) => ({ v: num2(r[key]), session: r.session || "run", ts: r.ts })).filter((x) => x.v != null); } function feelToTrend(feel) { const f = String(feel || "").toLowerCase(); if (/push|tight|tighten/.test(f)) return "slicking"; if (/free|loose|slide|lack/.test(f)) return "rubbering"; return null; } function snapToTrend(trend) { if (!trend || trend === "stable") return null; return trend === "drying_lanes" ? "slicking" : trend; } function extractPassiveTrackSignals(sessionLog = [], vehicleContext = {}) { const rows = sessionRows(sessionLog); const signals = []; const meas = vehicleContext.setup_measurements || {}; const stagger = series(rows, "stagger"); if (stagger.length >= 2) { const d = stagger[stagger.length - 1].v - stagger[0].v; if (d >= 0.2) { signals.push({ type: "stagger_out", trend: "slicking", label: `Stagger out ${d.toFixed(2)}" across log (${stagger[0].session} \u2192 ${stagger[stagger.length - 1].session})`, confidence: d >= 0.35 ? "moderate" : "low" }); } else if (d <= -0.2) { signals.push({ type: "stagger_in", trend: "rubbering", label: `Stagger in ${Math.abs(d).toFixed(2)}" across log`, confidence: d <= -0.35 ? "moderate" : "low" }); } } const rrPsi = series(rows, "rr_psi"); const rfPsi = series(rows, "rf_psi"); if (rrPsi.length >= 2) { const d = rrPsi[rrPsi.length - 1].v - rrPsi[0].v; if (d >= 1.5) { signals.push({ type: "rr_psi_up", trend: "slicking", label: `RR psi climbing +${d.toFixed(1)} (track freeing / tire working harder)`, confidence: d >= 2.5 ? "moderate" : "low" }); } } if (rfPsi.length >= 2) { const d = rfPsi[rfPsi.length - 1].v - rfPsi[0].v; if (d <= -1) { signals.push({ type: "rf_psi_down", trend: "slicking", label: `RF psi down ${d.toFixed(1)} (common slick-night wedge relief pattern)`, confidence: "low" }); } } const wing = series(rows, "wing_angle"); if (wing.length >= 2) { const d = wing[wing.length - 1].v - wing[0].v; if (d <= -0.4) { signals.push({ type: "wing_down", trend: "slicking", label: `Wing trimmed ${Math.abs(d).toFixed(1)}\xB0 across sessions`, confidence: "moderate" }); } else if (d >= 0.4) { signals.push({ type: "wing_up", trend: "rubbering", label: `Wing added ${d.toFixed(1)}\xB0 across sessions`, confidence: "low" }); } } const lrBar = series(rows, "lr_bar_turns"); if (lrBar.length >= 2) { const d = lrBar[lrBar.length - 1].v - lrBar[0].v; if (Math.abs(d) >= 0.5) { signals.push({ type: "bar_move", trend: d > 0 ? "rubbering" : "slicking", label: `LR bar ${d > 0 ? "in" : "out"} ${Math.abs(d).toFixed(1)} turns logged`, confidence: "low" }); } } const leftPct = series(rows, "left_pct"); const rearPct = series(rows, "rear_pct"); if (leftPct.length >= 2 && rearPct.length >= 2) { const cross0 = leftPct[0].v + rearPct[0].v - 100; const cross1 = leftPct[leftPct.length - 1].v + rearPct[rearPct.length - 1].v - 100; const d = cross1 - cross0; if (Math.abs(d) >= 0.4) { signals.push({ type: "cross_shift", trend: d > 0 ? "rubbering" : "slicking", label: `Cross shifted ${d > 0 ? "+" : ""}${d.toFixed(1)}% (${cross0.toFixed(1)} \u2192 ${cross1.toFixed(1)})`, confidence: "low" }); } } const rrTemp = series(rows, "rr_temp"); if (rrTemp.length >= 2) { const d = rrTemp[rrTemp.length - 1].v - rrTemp[0].v; if (d >= 12) { signals.push({ type: "rr_temp_climb", trend: "slicking", label: `RR temp up ${d.toFixed(0)}\xB0F \u2014 tire working harder as grip falls`, confidence: d >= 20 ? "moderate" : "low" }); } } const feels = rows.map((r) => r.driver_feel || r.feel).filter(Boolean); if (feels.length >= 2) { const early = feelToTrend(feels[0]); const late = feelToTrend(feels[feels.length - 1]); if (early === "slicking" && late === "slicking") { signals.push({ type: "feel_push", trend: "slicking", label: "Driver feel trending push/tight across sessions", confidence: "low" }); } else if (early === "rubbering" || late === "rubbering") { signals.push({ type: "feel_loose", trend: "rubbering", label: "Driver feel reported loose/free \u2014 grip building or setup over-scaled", confidence: "low" }); } } const traits = [ vehicleContext.trait_entry, vehicleContext.trait_mid, vehicleContext.trait_exit ].filter(Boolean); if (traits.length && /tight|push/.test(traits.join(" "))) { signals.push({ type: "trait_tight", trend: "slicking", label: "Car profile: tight/push feel on file", confidence: "low" }); } if (rows.length === 1 && meas.stagger != null && rows[0].stagger != null) { const d = num2(rows[0].stagger) - meas.stagger; if (Math.abs(d) >= 0.15) { signals.push({ type: "scale_vs_log", trend: d > 0 ? "slicking" : "rubbering", label: `Tonight's stagger ${d > 0 ? "above" : "below"} scale baseline`, confidence: "low" }); } } const trendVotes = {}; for (const s of signals) { if (!s.trend) continue; trendVotes[s.trend] = (trendVotes[s.trend] || 0) + (s.confidence === "moderate" ? 2 : 1); } let inferredTrend = "stable"; let best = 0; for (const [t, w] of Object.entries(trendVotes)) { if (w > best) { best = w; inferredTrend = t; } } if (!best) inferredTrend = "stable"; return { signals, inferredTrend, confidence: best >= 3 ? "moderate" : best >= 1 ? "low" : "none", signalCount: signals.length }; } function resolveTrackEvolutionConflict(sessionAnalysis, passiveSignals = {}, trackSnaps = [], opts = {}) { const recent = latestTrackSnap(trackSnaps, opts.maxSnapAgeMs); const sessionCount = sessionAnalysis.sessionLogCount ?? sessionAnalysis.sessionCount ?? 0; const loggedTrend = sessionAnalysis.trend || "stable"; const passiveTrend = passiveSignals.inferredTrend || "stable"; const snapTrend = snapToTrend(recent?.trend); let trend = loggedTrend; let trendSource = sessionCount >= 2 ? "session_log" : "blended"; let conflict = null; let resolution = "aligned"; const passiveWeight = passiveSignals.confidence === "moderate" ? 2 : passiveSignals.signalCount >= 2 ? 1 : 0; if (sessionCount >= 3) { trend = loggedTrend; trendSource = "session_log"; if (snapTrend && snapTrend !== loggedTrend && recent) { conflict = { kind: "snap_vs_log", message: `[CONFLICT] Snap "${recent.label}" vs logged ${loggedTrend} \u2014 session log wins (${sessionCount} runs)`, snapTrend, loggedTrend, winner: "session_log" }; resolution = "log_over_snap"; } else if (passiveTrend !== "stable" && passiveTrend !== loggedTrend && passiveWeight >= 2) { conflict = { kind: "passive_vs_log", message: `[CONFLICT] Passive signals (${passiveTrend}) vs logged ${loggedTrend} \u2014 logged track states win`, passiveTrend, loggedTrend, winner: "session_log" }; resolution = "log_over_passive"; } } else if (sessionCount >= 1 && passiveWeight >= 2 && passiveTrend !== "stable") { trend = passiveTrend; trendSource = "passive_signals"; if (snapTrend && snapTrend !== passiveTrend && recent) { conflict = { kind: "snap_vs_passive", message: `[CONFLICT] Snap "${recent.label}" vs passive ${passiveTrend} \u2014 passive scale/feel data leads`, snapTrend, passiveTrend, winner: "passive_signals" }; resolution = "passive_over_snap"; } } else if (snapTrend && snapTrend !== "stable") { trend = snapTrend; trendSource = "track_snap"; if (sessionCount >= 1 && loggedTrend !== "stable" && loggedTrend !== snapTrend) { conflict = { kind: "snap_vs_log_thin", message: `[CONFLICT] Thin log reads ${loggedTrend}, snap says ${recent.label} \u2014 snap leads until more sessions`, snapTrend, loggedTrend, winner: "track_snap" }; resolution = "snap_over_thin_log"; } } else if (passiveWeight >= 1 && passiveTrend !== "stable") { trend = passiveTrend; trendSource = "passive_signals"; } return { ...sessionAnalysis, trend, trendSource, recentSnap: recent, snapCount: Array.isArray(trackSnaps) ? trackSnaps.length : 0, passiveSignals, conflict, resolution, loggedTrend, passiveTrend, snapTrend }; } function buildEnrichedEvolutionContext(trackCondition = {}, trackState) { const sessionLog = trackCondition.session_log || []; const trackSnaps = trackCondition.track_snaps || []; const vehicleContext = trackCondition.vehicle_context || {}; const sessionAnalysis = analyzeSessionTrend(sessionLog, trackState); const passive = extractPassiveTrackSignals(sessionLog, vehicleContext); const analysis = resolveTrackEvolutionConflict(sessionAnalysis, passive, trackSnaps); const prediction = predictTrackEvolution(analysis, trackCondition); return { sessionLog, trackSnaps, passive, analysis, prediction, recentSnap: analysis.recentSnap, timeline: mergeEvolutionTimeline(sessionLog, trackSnaps) }; } function inferWhatWorked(sessionLog = []) { const rows = sessionRows(sessionLog); const worked = []; for (let i = 1; i < rows.length; i += 1) { const prev = rows[i - 1]; const cur = rows[i]; const finishDelta = num2(cur.finishing_position) != null && num2(prev.finishing_position) != null ? prev.finishing_position - cur.finishing_position : null; if (finishDelta != null && finishDelta >= 2) { const bits = [`${cur.session || "run"} improved P${prev.finishing_position} \u2192 P${cur.finishing_position}`]; if (cur.stagger != null && prev.stagger != null && cur.stagger !== prev.stagger) { bits.push(`stagger ${prev.stagger} \u2192 ${cur.stagger}`); } if (cur.note) bits.push(String(cur.note).slice(0, 60)); worked.push(bits.join(" \xB7 ")); } } if (!worked.length && rows.length >= 2) { const last = rows[rows.length - 1]; if (last.note) worked.push(`Last note (${last.session}): ${String(last.note).slice(0, 80)}`); } return worked.slice(0, 4); } function buildNightSummary(trackCondition = {}, trackState, profile2 = "") { const sessionLog = trackCondition.session_log || []; const trackSnaps = trackCondition.track_snaps || []; if (!sessionLog.length && !trackSnaps.length) { return { lines: [], worked: [], headline: null, hasContent: false }; } const evo = buildEnrichedEvolutionContext(trackCondition, trackState); const { analysis, prediction, passive } = evo; const lastSess = sessionLog[0] || sessionLog[sessionLog.length - 1]; const phase = classifyNightPhase(analysis.sessionLogCount ?? analysis.sessionCount, lastSess?.session); const worked = inferWhatWorked(sessionLog); const lines = []; const first = analysis.firstState?.replace("_", " ") || "unknown"; const last = analysis.lastState?.replace("_", " ") || trackState || "unknown"; lines.push(`Track arc: ${first} \u2192 ${last} (${analysis.trend.replace("_", " ")}) \xB7 source: ${analysis.trendSource.replace("_", " ")}`); if (analysis.loggedTrend) lines.push(`Session chain: ${analysis.loggedTrend}`); if (passive.signals.length) { lines.push(`Passive read: ${passive.signals.slice(0, 2).map((s) => s.label).join(" \xB7 ")}`); } if (analysis.conflict?.message) lines.push(analysis.conflict.message); if (prediction.likelyNext && prediction.likelyNext !== analysis.lastState) { lines.push(`Likely next: ${prediction.likelyNext.replace("_", " ")} (${prediction.confidence} confidence)`); } if (phase === "late") lines.push("Late night: protect tires \u2014 one lever committed beats a hero reset"); const recentSnap = latestTrackSnap(trackSnaps); if (recentSnap) { lines.push(`${USER_REPORTED_TAG} ${recentSnap.session}: ${recentSnap.label}${recentSnap.note ? ` \u2014 ${recentSnap.note.slice(0, 48)}` : ""}`); } const headline = worked.length ? `Night summary \xB7 ${analysis.trend.replace("_", " ")} \xB7 ${worked[0]}` : `Night summary \xB7 track ${analysis.trend.replace("_", " ")} (${first} \u2192 ${last})`; return { headline, lines: lines.slice(0, 7), worked, trend: analysis.trend, trendSource: analysis.trendSource, conflict: analysis.conflict, passiveSignals: passive.signals, nightPhase: phase, hasContent: true, profile: profile2 }; } // scripts/lib/experimental/lateModelTrackEvolution.mjs var LATE_MODEL_EVO_PROFILES = /* @__PURE__ */ new Set(["late_model", "modified_dirt"]); function isLateModelEvolutionProfile(profile2) { return LATE_MODEL_EVO_PROFILES.has(profile2); } function sessionLabel2(row) { return String(row?.session || row?.phase || "run").trim(); } function lateModelTrackEvolutionPriors(enrichedVc = {}, trackState, trackCondition = {}, opts = {}) { const profile2 = opts.profile || "late_model"; if (!isLateModelEvolutionProfile(profile2)) return []; const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const max = opts.maxPriors ?? 5; const tc = trackCondition || {}; const evoCtx = opts.evoCtx || buildEnrichedEvolutionContext({ ...tc, vehicle_context: enrichedVc }, trackState); const analysis = evoCtx.analysis; const prediction = evoCtx.prediction; const lastSess = (tc.session_log || [])[0] || null; const phase = classifyNightPhase(analysis.sessionLogCount ?? analysis.sessionCount, sessionLabel2(lastSess)); const priors = []; const isMod = profile2 === "modified_dirt"; if (analysis.conflict?.message) { priors.push({ lever: "lm_evo_conflict", action: analysis.conflict.message.replace(/^\[CONFLICT\]\s*/, ""), reason: tagReason("[USER REPORTED]", "Manual snap vs logged data \u2014 follow the winner noted above") }); } if (analysis.trend === "slicking" || prediction.likelyNext === "dry_slick" || prediction.likelyNext === "slick") { priors.push({ lever: "lm_evo_slick_cross", action: isMod ? "If track continues to slick: RF wedge relief OR stagger out 1/8\u20131/4 in BEFORE chasing LR weight \u2014 modified torque wants mechanical balance first" : "If track continues to slick: RF wedge down 1/8\u20131/4 turn OR stagger out 1/8 in \u2014 cross stable until RR temp proves you need more rear", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Late model slick nights = wedge/stagger before cross hero moves") }); priors.push({ lever: "lm_evo_slick_compound", action: "Compound ladder: if RR temp spikes or hop returns, step down one compound (D40\u2192D30) before another cross move \u2014 log hot psi each session", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "LM/mod nights often end on compound + psi, not endless cross") }); } if (analysis.trend === "rubbering" || prediction.likelyNext === "heavy") { priors.push({ lever: "lm_evo_rubber_stagger", action: "If rubber keeps building: stagger IN 1/8\u20131/4 in vs slick setup \xB7 protect RR temp \xB7 wedge add only after hop is gone", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Rubber-up flips the slick-night stagger direction on LM/mod") }); } if (analysis.loggedTrend && analysis.sessionCount >= 2) { priors.push({ lever: "lm_evo_session_chain", action: `Logged chain (${analysis.loggedTrend}): inherit ONE planned change \u2014 cross OR stagger OR compound, not all three`, reason: tagReason(ADVANCED_SOURCE.YOUR_DATA, "Multi-session log is the authority on this track tonight") }); } if (prediction.likelyNext && prediction.likelyNext !== analysis.lastState) { priors.push({ lever: "lm_evo_forward_plan", action: `If track continues toward ${prediction.likelyNext.replace("_", " ")}: pre-plan wedge OR stagger (pick one) before rolling out \u2014 ${(analysis.lastState || trackState || "current").replace("_", " ")} \u2192 likely ${prediction.likelyNext.replace("_", " ")}`, reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Forward state beats reacting only to the last lap") }); } if (phase === "late") { priors.push({ lever: "lm_evo_late_night", action: "Feature/consi: commit lane + compound now \u2014 one wedge or stagger move max; protect RR on long runs", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Late LM/mod = tire and lane management over platform resets") }); } const lmModExtras = lmModEvolutionAdjustments(enrichedVc, trackState, analysis, profile2); priors.push(...lmModExtras); return priors.filter((p) => !skip.has(p.lever)).slice(0, max); } function buildLateModelMultiSessionPlanning(trackCondition = {}, enrichedVc = {}, trackState, opts = {}) { const profile2 = opts.profile || "late_model"; if (!isLateModelEvolutionProfile(profile2)) { return { lines: [], trend: "unknown", sessionCount: 0 }; } const evoCtx = opts.evoCtx || buildEnrichedEvolutionContext( { ...trackCondition, vehicle_context: enrichedVc }, trackState ); const analysis = evoCtx.analysis; const prediction = evoCtx.prediction; const isMod = profile2 === "modified_dirt"; const lines = []; const snapLine = trackSnapPlanningLine(evoCtx.recentSnap, analysis); if (snapLine) lines.push(snapLine); if (analysis.conflict?.message) lines.push(analysis.conflict.message); if (evoCtx.passive?.signals?.length) { lines.push(`Passive: ${evoCtx.passive.signals.slice(0, 2).map((s) => s.label).join(" \xB7 ")}`); } if (analysis.trend === "slicking") { lines.push( isMod ? "If track keeps slicking: RF wedge OR stagger out 1/8\u20131/4 in \u2014 one lever per session" : "If track keeps slicking: RF wedge down OR stagger out 1/8 in \u2014 cross stable until RR temp forces a move" ); } else if (analysis.trend === "rubbering") { lines.push("If rubber builds: stagger in vs slick setup \xB7 watch RR \xB7 compound down before cross chase"); } else if (analysis.trend === "cooling") { lines.push("If track keeps cooling: step back toward tacky baseline (psi down, slight wedge back in)"); } if (analysis.loggedTrend && analysis.sessionCount >= 2) { lines.push(`Logged trend (${analysis.loggedTrend}): inherit ONE change \u2014 do not reset platform mid-chain`); } if (prediction.likelyNext && prediction.likelyNext !== analysis.lastState) { lines.push( `Likely next: ${prediction.likelyNext.replace("_", " ")} (${prediction.confidence} confidence)` ); } return { trend: analysis.trend, laneTrend: analysis.laneTrend, likelyNext: prediction.likelyNext, confidence: prediction.confidence, sessionCount: analysis.sessionCount, nightPhase: classifyNightPhase(analysis.sessionLogCount ?? analysis.sessionCount, sessionLabel2((trackCondition.session_log || [])[0])), loggedTrend: analysis.loggedTrend, trendSource: analysis.trendSource, conflict: analysis.conflict, recentSnap: evoCtx.recentSnap, lines: lines.slice(0, 6) }; } // scripts/lib/experimental/lateModelPivotPrinciples.mjs var LM_MOD_PROFILES = /* @__PURE__ */ new Set(["late_model", "modified_dirt"]); var LM_MOD_MULTI_SHOCK_OPTIONS = [ { id: "none", label: "Single shock per corner (traditional)" }, { id: "primary_only", label: "Primary rear shocks only \u2014 no secondary/can" }, { id: "dual_lr_rr", label: "Dual rear \u2014 primary + secondary shock or can (LR/RR)" }, { id: "dual_with_bump", label: "Dual rear + bump stop / third damper package" } ]; var LM_MOD_TRAILING_ARM_STYLE = [ { id: "stock", label: "Stock / builder-length trailing arms" }, { id: "compact", label: "Compact / shortened trailing arm package" }, { id: "unknown", label: "Unknown / not sure" } ]; var LM_MOD_REQUIRED_PICKUPS = [ "panhard_frame", "j_bar_frame", "lr_trailing_arm", "rr_trailing_arm", "lr_birdcage_outer", "rr_birdcage_outer", "lf_upper_inner", "rf_upper_inner", "lf_shock_mount", "rf_shock_mount", "lr_shock_mount", "rr_shock_mount" ]; var LM_MOD_OPTIONAL_PICKUPS = [ "lr_secondary_shock", "rr_secondary_shock" ]; var LM_MOD_PICKUP_LABELS = { lr_birdcage_outer: "LR birdcage / trailing arm outer", rr_birdcage_outer: "RR birdcage / trailing arm outer", lr_secondary_shock: "LR secondary shock / can mount", rr_secondary_shock: "RR secondary shock / can mount" }; var LM_MOD_PLATFORM_MODS = [ { id: "multi_shock_package", label: "Multi-shock rear package added or changed" }, { id: "shock_tab_relocation", label: "Shock mount / tab relocated" }, { id: "trailing_arm_length", label: "Trailing arm length / compact package" }, { id: "birdcage_reindex", label: "Birdcage re-indexed or swapped" }, { id: "panhard_jbar_mount_moved", label: "Panhard / J-bar mount moved" }, { id: "other", label: "Other structural change" } ]; function isLmModPivotClass(carType, profile2 = "") { const ct = String(carType || "").toLowerCase(); if (ct === "latemodel" || ct === "modified") return true; return LM_MOD_PROFILES.has(profile2); } function normalizeLmModPlatform(raw = {}) { const pi = raw && typeof raw === "object" ? raw : {}; return { multi_shock_rear: pi.multi_shock_rear || null, compact_trailing_arm: pi.compact_trailing_arm || null }; } function readLmModPlatform(vc2 = {}) { const pi = vc2.platform_integrity || vc2.geometry_state?.platform_integrity || {}; return normalizeLmModPlatform(pi); } function hasMultiShockRear(vc2 = {}) { const { multi_shock_rear: ms } = readLmModPlatform(vc2); return ms === "dual_lr_rr" || ms === "dual_with_bump"; } function expectedLmModPickupKeys(vc2 = {}, carType) { const ct = String(carType || vc2.car_type || "").toLowerCase(); if (!isLmModPivotClass(ct)) return null; const keys = [...LM_MOD_REQUIRED_PICKUPS]; if (hasMultiShockRear(vc2)) keys.push(...LM_MOD_OPTIONAL_PICKUPS); return keys; } function scoreLmModPickups(pp = {}, vc2 = {}) { const required = LM_MOD_REQUIRED_PICKUPS; const optional = hasMultiShockRear(vc2) ? LM_MOD_OPTIONAL_PICKUPS : []; const measured = []; const missing = []; for (const key of required) { const pt = pp[key]; if (pt && typeof pt === "object" && pt.x != null) measured.push(key); else missing.push(key); } const optionalMeasured = []; const optionalMissing = []; for (const key of optional) { const pt = pp[key]; if (pt && typeof pt === "object" && pt.x != null) optionalMeasured.push(key); else optionalMissing.push(key); } const ratio = required.length ? measured.length / required.length : 0; return { required, optional, measured, missing, optionalMeasured, optionalMissing, ratio }; } function lmModPivotCoachLine(pivot = {}, vc2 = {}) { const plat = readLmModPlatform(vc2); const missingShocks = (pivot.missing || []).filter((k) => /shock_mount/.test(k)); const missingBirdcage = (pivot.missing || []).filter((k) => /birdcage_outer/.test(k)); if (pivot.stale) { return "PIVOT stale \u2014 re-measure LR/RR shock mounts and birdcage pickups after platform change"; } if (pivot.complete) { if (hasMultiShockRear(vc2) && (pivot.optionalMissing?.length || 0) > 0) { return `PIVOT: core pickups on file \u2014 log secondary/can mounts for your ${plat.multi_shock_rear?.replace(/_/g, " ")} package`; } return "PIVOT: LM/Mod geometry anchored \u2014 shock mounts + birdcage outers enable repeatable RC comparisons"; } if (missingShocks.length) { return `PIVOT: ${pivot.measured}/${pivot.expected} \u2014 rear shock mount locations drive instant center on modern LM/Mod packages`; } if (missingBirdcage.length) { return `PIVOT: ${pivot.measured}/${pivot.expected} \u2014 birdcage outer pickups separate compact trailing-arm geometry from builder charts`; } return pivot.coachLine || `PIVOT: ${pivot.measured}/${pivot.expected} \u2014 panhard/J-bar + trailing arm inners/outers + shock mounts`; } function lmModModernSetupPriors(vc2 = {}, trackState, profile2 = "late_model") { if (!isLateModelEvolutionProfile(profile2)) return []; const isMod = profile2 === "modified_dirt"; const track = String(trackState || "").toLowerCase(); const slick = /slick|drying|heavy/.test(track); const plat = readLmModPlatform(vc2); const m = vc2.setup_measurements || {}; const priors = []; priors.push({ lever: "lm_mod_controlled_roll", action: isMod ? "Controlled roll philosophy: allow some body roll to load RR \u2014 stiffening the front to flatten usually hurts slick-track balance before wedge/stagger" : "Controlled roll philosophy: competitive LM setups use some roll for RR load \u2014 chase secondary/can or wedge before zero-roll spring hero moves", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Modern LM/mod \u2014 roll is a tool, not the enemy"), flags: { advanced_chassis: true, lm_mod_modern: true } }); if (plat.compact_trailing_arm === "compact") { priors.push({ lever: "lm_mod_compact_geometry", action: "Compact trailing-arm package: smaller geometry changes move the car \u2014 one panhard hole or J-bar step per session, log birdcage index", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Short trailing-arm cars respond quickly to pickup changes"), flags: { advanced_chassis: true, lm_mod_modern: true } }); } if (hasMultiShockRear(vc2)) { priors.push({ lever: "lm_mod_multi_shock", action: slick ? "Multi-shock rear: on slick/heavy tracks tune secondary/can or bump engagement BEFORE adding wedge \u2014 primary shock handles roll, secondary controls bottoming and weight transfer" : "Multi-shock rear: primary shock = roll platform \xB7 secondary/can = bottoming control \u2014 adjust one circuit per run, log clicks on both", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Dual rear damping is standard on top LM/mod teams"), flags: { advanced_chassis: true, lm_mod_modern: true, builder_signal: true } }); } else { priors.push({ lever: "lm_mod_shock_mount_ref", action: "Log all four shock mount pickups in PIVOT \u2014 modern LM/mod instant-center work starts at mount location, not shock clicks alone", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Shock location \u2260 adjustment hole on competitive rear packages"), flags: { advanced_chassis: true, lm_mod_modern: true, scaffold: !vc2.pickup_points_count } }); } if (m.ride_ht_lr != null && m.ride_ht_rr != null) { const avgR = (Number(m.ride_ht_lr) + Number(m.ride_ht_rr)) / 2; if (avgR <= 3.25) { priors.push({ lever: "lm_mod_low_rh", action: "Low ride height on file \u2014 re-AFRH each session before cross/wedge; tire growth and rubber build change effective RH on long runs", reason: tagReason(ADVANCED_SOURCE.YOUR_DATA, "Low platform LM/mod \u2014 RH drift is a between-session lever"), flags: { advanced_chassis: true, lm_mod_modern: true, user_data: true } }); } } priors.push({ lever: "lm_mod_balanced_window", action: isMod ? "Balanced window: moderate stagger and panhard before extreme cross \u2014 modified nights reward one lever that works tacky through slick" : "Balanced window: avoid extreme stagger splits \u2014 competitive LM setups adapt across rubber-up and slick phases with wedge OR stagger, not both stacked", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Top teams optimize for changing track state, not one perfect lap"), flags: { advanced_chassis: true, lm_mod_modern: true } }); return priors; } function lmModEvolutionAdjustments(vc2 = {}, trackState, analysis = {}, profile2 = "late_model") { if (!isLateModelEvolutionProfile(profile2)) return []; const track = String(trackState || analysis.lastState || "").toLowerCase(); const slick = analysis.trend === "slicking" || /slick|drying/.test(track); const rubber = analysis.trend === "rubbering" || /tacky|rubber|heavy/.test(track); const priors = []; if (hasMultiShockRear(vc2) && slick) { priors.push({ lever: "lm_evo_multi_shock_slick", action: "Track slicking + dual rear shocks: soften secondary/can engagement OR add 1\u20132 clicks secondary compression before RF wedge \u2014 controls bottoming without killing roll", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Multi-shock slick-night order \u2014 secondary before wedge stack") }); } if (hasMultiShockRear(vc2) && rubber) { priors.push({ lever: "lm_evo_multi_shock_rubber", action: "Rubber building + dual rear shocks: primary shock may need more rebound control \u2014 stiffen primary 1 click before touching secondary/can on long runs", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Rubber-up shifts which damper circuit carries the platform") }); } if (slick) { priors.push({ lever: "lm_evo_controlled_roll_slick", action: "Slick evolution: resist stiffening front to kill roll \u2014 let controlled roll work; adjust panhard/J-bar or secondary damping first", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Flat-car obsession hurts RR load on slick LM/mod nights") }); } if (rubber) { priors.push({ lever: "lm_evo_rh_rubber", action: "Rubber-up: verify ride height before stagger in \u2014 low platform cars pick up grip as RH drops from tire growth", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Between-session RH check on competitive LM/mod packages") }); } return priors; } function pickLmModPivotBriefLine(vc2 = {}) { const plat = readLmModPlatform(vc2); const parts = []; if (plat.multi_shock_rear && plat.multi_shock_rear !== "none") { parts.push(`Rear damping: ${plat.multi_shock_rear.replace(/_/g, " ")}`); } if (plat.compact_trailing_arm === "compact") parts.push("Compact trailing-arm package"); if (vc2.platform_integrity?.birdcage_reindexed) parts.push("Birdcage re-indexed"); return parts.length ? parts.join(" \xB7 ") : null; } // scripts/lib/experimental/multiShockDamperKnowledge.mjs var HORIZONTAL_THIRD_DAMPER_PRIORS = [ { lever: "horizontal_damper_role", action: "Horizontal (third) damper: mounted fore-aft on the rear \u2014 controls pitch under braking/acceleration and limits axle wrap. On LM/Mod it works WITH primary LR/RR shocks, not instead of panhard/4-link baseline.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Competitive trailing-arm LM teams use horizontal/can packages to manage pitch separate from roll \u2014 log whether yours is compression-only or bidirectional") }, { lever: "third_damper_can_role", action: "Third damper / can (LR or RR secondary): adds a second rate in compression \u2014 often for bottoming control on big bumps or to hold platform through transition without stiffening the primary shock for roll.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Dual-shock rear splits roll (primary) from pitch/bottoming (secondary/can) \u2014 wrong valving feels like loose entry / tight off confusion") }, { lever: "lm_mod_dual_shock_order", action: "LM/Mod dual rear: set primary shock platform first (rebound/comp baseline) \u2192 log secondary/can valving \u2192 panhard/IC \u2192 cross. Change ONE package at a time \u2014 secondary too stiff tightens exit; too soft lets axle wrap on entry.", reason: tagReason(ADVANCED_SOURCE.YOUR_DATA, "Multi-shock LM/Mod: measured primary + secondary mount refs beat generic valving charts") }, { lever: "sprint_coilover_conversion", action: "Sprint/midget coilover conversion (experimental): coil replaces torsion on one or more corners \u2014 spring rate + shock valving become paired levers. Re-index ride height from blocks/bars; RC/panhard baseline unchanged but rate steps feel sharper than bar turns.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Coilover swap changes rate resolution \u2014 document spring rate and collar position; do not copy torsion bar pound numbers 1:1") }, { lever: "sprint_secondary_experimental", action: "Sprint/midget adding secondary/can (off-the-wall): rare but seen on heavy tracks \u2014 secondary usually limits extension/bottoming, not primary roll. If loose entry after adding can, primary rebound may be too slow OR can is fighting axle rotation.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Light cars: small secondary changes have outsized phase effects \u2014 A-B-A with phase notes mandatory") }, { lever: "horizontal_vs_lr_stop", action: "Horizontal/third damper is NOT an LR stop substitute: if tight off, fix primary platform (panhard/J-bar or 4-link IC) before stiffening secondary. Horizontal package that is too stiff on compression can mimic push entry on trailing-arm cars.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Parents from sprint vocabulary often confuse third damper with LR stop \u2014 different axis (pitch vs roll steer)") }, { lever: "multi_shock_pivot_log", action: "When running dual rear: log PIVOT secondary shock mount refs (LR/RR can) + platform flag multi_shock_rear \u2014 Crew Chief uses your mounts before suggesting secondary valving changes.", reason: tagReason(ADVANCED_SOURCE.YOUR_DATA, "Builder priors reference; your logged package type is authority") } ]; function multiShockDamperPriors(vc2 = {}, profile2 = "") { const ct = String(vc2.car_type || "").toLowerCase(); const cls = String(vc2.car_class || vc2.class || "").toLowerCase(); const lmMod = profile2 === "late_model" || profile2 === "modified_dirt" || ct === "latemodel" || ct === "modified" || ct === "modlite"; const sprintMidget = ct === "sprint" || ct === "jrsprint" || ct === "micro" || ct === "micro270" || /sprint|midget|410|360|305|usac/.test(cls); const platform = readLmModPlatform(vc2); const mods = vc2.platform_integrity?.modifications || vc2.geometry_state?.platform_integrity?.modifications || []; const coilConversion = mods.includes("coilover_conversion") || /coilover|coil.over|coil over conversion|coil swap/.test(cls + String(vc2.pivot_pickup_notes || "")); const list = []; if (lmMod && (hasMultiShockRear(vc2) || platform.multi_shock_rear)) { list.push(...HORIZONTAL_THIRD_DAMPER_PRIORS.filter( (p) => ["horizontal_damper_role", "third_damper_can_role", "lm_mod_dual_shock_order", "horizontal_vs_lr_stop", "multi_shock_pivot_log"].includes(p.lever) )); } if (sprintMidget && (coilConversion || mods.includes("shock_tab_relocation"))) { list.push(...HORIZONTAL_THIRD_DAMPER_PRIORS.filter( (p) => ["sprint_coilover_conversion", "sprint_secondary_experimental", "horizontal_vs_lr_stop", "multi_shock_pivot_log"].includes(p.lever) )); } if (lmMod && !list.length && profile2 === "late_model") { list.push(HORIZONTAL_THIRD_DAMPER_PRIORS[0], HORIZONTAL_THIRD_DAMPER_PRIORS[6]); } return list.slice(0, 4); } var MULTI_SHOCK_MATCH_TERMS = [ "horizontal damper", "third damper", "horizontal shock", "third shock", "can shock", "secondary shock", "dual shock rear", "multi shock", "multi-shock", "bump stop package", "coilover conversion", "coil over conversion", "coil swap sprint", "added a can", "lr can", "rr can" ]; function pickMultiShockBriefLine(vc2 = {}) { const priors = multiShockDamperPriors(vc2); return priors[0]?.action?.slice(0, 120) || null; } // scripts/lib/experimental/lightningSprintBaselinePriors.mjs function normalizeSurfaceState10(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var LIGHTNING_SPRINT_PUBLIC_BASELINE_CAVEAT = "Public baseline / starting point from available manufacturer knowledge (primarily Hyper Racing) \u2014 validate with your own data and chassis-specific setup."; var LIGHTNING_SPRINT_BASELINE = { source: "Hyper Racing Lightning Sprint setup guides (public winged + wingless)", tire_psi: { winged: { lf: "10", rf: "10", lr: "4-10", rr: "5-12" }, wingless: { lf: "10", rf: "10", lr: "4-6", rr: "5-8" } }, stagger_rear_in: { tacky: "5-10", drying: "5-8", slick: "4-7", greasy: "5-10", unknown: "5-10" }, ride_height_in: { winged: "LF 7-1/2 / RF 8-7/8 / LR 9-1/4 / RR 11-1/8 \u2014 ground \u2192 torsion bar center, no driver", wingless: "Ground \u2192 torsion bar center (no driver) \u2014 wingless blocks differ; verify Hyper wingless sheet", front_tube: "Also reference front tube / cross-member marks per Hyper squaring procedure \u2014 same driver-in/out every time" }, torsion_bars: { winged: { lf: "140 coil", rf: "150 coil", lr: ".800", rr: ".825" }, wingless: { lf: "150 coil", rf: "165 coil", lr: ".800", rr: ".775" } }, blocks_winged: '3" LF/RF, 3-1/4" LR, 3-1/2" RR (small O.D. axle reference)', blocks_wingless: '2-1/2" LF/RF, 3" LR, 3-1/4" RR (small O.D. axle reference)', geometry: "Axles square \xB7 toe 0 \xB7 caster ~10\xB0 \xB7 RR carrier 4\xB0 forward \xB7 front panhard ~4 in \xB7 chain aligned", wing_angle: "~26\xB0 top wing on 1/4 mi or smaller; ~18\xB0 on bigger tracks (Hyper winged guide)", wheel_note: '13" wheels \u2014 not 600 micro 10" geometry' }; function isLightningSprintProfile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); if (/600 micro|restricted micro|now600|\b270\b|quarter.midget|qma/.test(c)) return false; if (ct === "lightningsprint") return true; if (/lightning|glls|great lakes lightning/.test(c)) return true; if (/mini sprint/i.test(c) && !/600|micro|quarter|270/.test(c)) return true; return false; } function resolveLightningSprintChassisHint(vc2 = {}) { return resolveGrassrootsChassisHint("lightning_sprint", vc2); } function isWingless2(vc2 = {}) { const c = String(vc2.car_class || vc2.class_name || "").toLowerCase(); if (vc2.winged === false) return true; if (/non.?wing|wingless|no.?wing/.test(c)) return true; return false; } var L = LIGHTNING_SPRINT_BASELINE; var STG5 = L.stagger_rear_in; function wingConfig(wingless) { if (wingless) { return { wing: "Wingless: no top-wing \u2014 balance with Jacobs ladder hole, ride height/tilt, and LF shock tie-down", ride: `${L.ride_height_in.wingless}; ${L.ride_height_in.front_tube}`, blocks: L.blocks_wingless, bars: L.torsion_bars.wingless, psi: L.tire_psi.wingless, jacobs: "Jacobs ladder: start left-side hole (no driver) \u2014 wet/tight may move to right hole; lengthen rod end to hold axle position", lf_shock: "Wingless LF shock: run tie-down \u2014 reduce LF tie-down to tighten coming off corner (Hyper wingless note)", shock_start: "Hyper wingless adjustable ref: LR full stiff \u22124 turns, RR full stiff (soft comp on adj RR), RF \u22121-1/2, LF \u22124 turns", tighten: "Tighten dirt: less stagger (to ~4 in), lower RR/LR psi, RR in ~11 in, raise ride height (+1 RS / +2 LS), soften RR bar, more RR rebound or less RR comp", loosen: "Loosen dirt: more stagger, stiffen RR comp + LR rebound, raise RR psi, RR out to 14 in, Jacobs ladder right hole, lower ride heights (add tilt)" }; } return { wing: `Winged: ${L.wing_angle} \u2014 32" nose wing; wing back + angle to tighten, forward to loosen`, ride: `${L.ride_height_in.winged}; ${L.ride_height_in.front_tube}`, blocks: L.blocks_winged, bars: L.torsion_bars.winged, psi: L.tire_psi.winged, jacobs: "Jacobs ladder: start right-side hole without driver \u2014 slick may move to left hole with rod-end lengthened", lf_shock: "Winged: run LF tie-down per Hyper setup notes \u2014 shock valving before removing tie-down", shock_start: "Hyper winged adjustable ref: LR full stiff \u22121-1/2 turns, RR \u22121 turn, RF \u22121-1/2, LF \u22121 turn", tighten: "Tighten dirt: wing back, less stagger, lower RR/LR psi, RR in, raise ride height (+1 RS / +2 LS), RR shock soft comp + stiff RF comp", loosen: "Loosen dirt: wing forward, more stagger, raise RF/RR psi, RR out, lower ride heights front/rear (add tilt), stiffen RR bar" }; } var PRIORS_BY_BUCKET10 = { tacky: (wingless) => { const w = wingConfig(wingless); const PSI7 = w.psi; return [ { lever: "geometry", action: `Mechanical baseline first: ${L.geometry} \u2014 square axles before ride height or stagger`, reason: "public baseline (lightning): Hyper setup notes \u2014 geometry errors read like sprint, not kart" }, { lever: "squaring", action: "Square rear axle to front tube / roll cage reference \u2014 Hyper squaring kit or manual procedure", reason: 'public baseline (lightning): repeatable baseline starts with squared axles and correct tire offsets (LF 3"/4" wheel split)' }, { lever: "baseline", action: "Build repeatable baseline sheet (blocks, bars, wing/Jacobs, stagger) \u2014 return here when the car loses the handle", reason: "public baseline (lightning): Hyper Lightning guides are structured as return-to baseline setups" }, { lever: "ride_height", action: `Set ride heights (${w.ride})`, reason: "public baseline (lightning): always no-driver OR always driver \u2014 never mix comparisons with other handlers" }, { lever: "tire_pressure", action: `Log starting pressures: LF ${PSI7.lf} / RF ${PSI7.rf} / LR ${PSI7.lr} / RR ${PSI7.rr} psi (Hyper normal 1/6\u20131/8 band)`, reason: "public baseline (lightning): right-side stiffer loosens, left-side stiffer tightens \u2014 LR psi also affects effective stagger" }, { lever: "stagger", action: `Start rear stagger ~${wingless ? "5" : "6"} in (${STG5.tacky} in band on 13" tires) \u2014 remeasure hot after laps 8-10`, reason: 'public baseline (lightning): more stagger loosens, less tightens \u2014 not 600 micro 10" wheel math' }, { lever: "torsion", action: `Torsion/coil starting ref: LF ${w.bars.lf} / RF ${w.bars.rf} / LR ${w.bars.lr} / RR ${w.bars.rr} \u2014 coil \u22484 turns per 1 bar turn`, reason: "public baseline (lightning): bar size and arm length are chassis-specific \u2014 verify your year/suspension guide" }, { lever: "blocks", action: `Block baseline: ${w.blocks}`, reason: "public baseline (lightning): blocks + turns set ride height \u2014 heavy driver (>220 lb) may need stiffer rear bars + RR out" }, { lever: "jacobs", action: w.jacobs, reason: "public baseline (lightning): Jacobs ladder hole is a primary wingless balance tool; winged uses it on slick transition" }, { lever: "wing", action: w.wing, reason: "public baseline (lightning): winged vs wingless are separate Hyper PDFs \u2014 not interchangeable baselines" }, { lever: "shock", action: `${w.shock_start}. ${w.lf_shock}`, reason: "public baseline (lightning): wingless LF tie-down is an exit-balance lever \u2014 one adj at a time after baseline" }, { lever: "direction", action: w.tighten, reason: "public baseline (lightning): Hyper dirt tighten list \u2014 follow order, one lever per run" }, { lever: "discipline", action: "One lever per run \u2014 do not stack wing/Jacobs + stagger + psi same heat", reason: "public baseline (lightning): isolate variables so logged A-B-A replaces these priors quickly" } ]; }, drying: (wingless) => { const w = wingConfig(wingless); return [ { lever: "stagger", action: `As track frees, take 1/4 in stagger out (toward ${STG5.drying} in) before shock chasing`, reason: "public baseline (lightning): less stagger first as rubber builds \u2014 midget-scale dirt philosophy" }, { lever: "rr", action: "Ease RR psi down as track slicks (1 psi steps) \u2014 primary Hyper tighten move on freeing track", reason: "public baseline (lightning): protect RR for drive as track frees" }, { lever: "jacobs", action: wingless ? "Wingless drying/slick: Jacobs ladder toward right hole if tight on wet \u2014 lengthen rod end to hold axle position" : "Winged drying: Jacobs toward left hole on slick; wing back if still tight", reason: "public baseline (lightning): Hyper Jacobs ladder hole change is a small but useful transition tool" }, { lever: "ride_height", action: "Entry push while drying: +1 turn RS / +2 turns LS ride height for forward bite (Hyper small track)", reason: "public baseline (lightning): raising ride height tightens mid/exit \u2014 take tilt out on smaller tracks" }, { lever: "direction", action: w.loosen, reason: "public baseline (lightning): if car binds while drying, Hyper loosen list \u2014 stagger/RR psi first" } ]; }, slick: (wingless) => { const w = wingConfig(wingless); return [ { lever: "stagger", action: `To tighten on slick: reduce stagger toward ${STG5.slick} in (smaller LR roll-out) \u2014 Hyper lists as low as 4 in`, reason: "public baseline (lightning): less stagger is first slick tighten move on dirt" }, { lever: "rr", action: "To tighten: lower RR psi (toward 5-6) and LR \u2014 move RR in toward 11 in offset", reason: "public baseline (lightning): RR in + lower RR psi before bar changes on slick" }, { lever: "shock", action: wingless ? "Wingless slick tighten: more RR rebound or less RR compression; loosen LF tie-down to tighten off" : "Winged slick tighten: RR shock full soft comp + stiffen RF comp; wing back", reason: "public baseline (lightning): wingless uses LF tie-down where winged uses wing + RF comp" }, { lever: "ride_height", action: "Loosen exit on slick: lower ride heights 2-8 turns front, 1-3 rear (add tilt) \u2014 Hyper loosen list", reason: "public baseline (lightning): exit balance uses ride height + tilt on lightning chassis" }, { lever: "direction", action: w.tighten, reason: "public baseline (lightning): Hyper ordered dirt tighten moves \u2014 confirm with A-B-A" } ]; }, greasy: (wingless) => { const w = wingConfig(wingless); return [ { lever: "stagger", action: `Heavy/greasy: upper stagger band (${STG5.greasy} in) \u2014 1/4 in steps from baseline`, reason: "public baseline (lightning): more stagger loosens \u2014 wet often wants wider band before geometry" }, { lever: "tire_pressure", action: "Greasy: run upper psi band on right side \u2014 still 1 psi per change", reason: 'public baseline (lightning): tacky/wet carries more psi than slick on 13" dirt tires' }, { lever: "jacobs", action: wingless ? "Wingless wet/tight: Jacobs ladder to right-side hole \u2014 lengthen rod end to preserve axle position" : w.jacobs, reason: "public baseline (lightning): Hyper wingless wet-track note \u2014 ladder before big shock moves" }, { lever: "geometry", action: "Recheck LF tie-down, chain alignment, panhard (~4 in front), and carrier timing before cross chase", reason: "public baseline (lightning): Hyper mechanical checklist before tuning on heavy tracks" } ]; }, unknown: (wingless) => { const w = wingConfig(wingless); const PSI7 = w.psi; return [ { lever: "class_note", action: 'Lightning Sprint \u2260 600 Micro \u2014 13" wheels, 1000-1200cc, midget-scale setup philosophy', reason: "public baseline (lightning): do not use 600 micro or full 305/360 sprint hard numbers" }, { lever: "geometry", action: L.geometry, reason: "public baseline (lightning): square and time the car before ride height, stagger, or scaling" }, { lever: "ride_height", action: `Ride height ref: ${w.ride}`, reason: "public baseline (lightning): ground \u2192 torsion bar center; front tube marks for squaring consistency" }, { lever: "tire_pressure", action: `Reference band: LF ${PSI7.lf} / RF ${PSI7.rf} / LR ${PSI7.lr} / RR ${PSI7.rr} psi`, reason: "public baseline (lightning): Hyper normal 1/6\u20131/8 starting band" }, { lever: "stagger", action: `Rear stagger start ~${wingless ? "5" : "6"} in (${STG5.unknown} in on 13" tires)`, reason: "public baseline (lightning): stagger band overlaps midget thinking more than micro kart scale" }, { lever: "chassis", action: "Log chassis manufacturer + model in Garage \u2192 Setup \u2192 Chassis for routed lightning advice", reason: "public baseline (lightning): Hyper publishes separate winged vs wingless guides" }, { lever: "discipline", action: "One DOF per run \u2014 confirm with A-B-A before feature", reason: "public baseline (lightning): community + Hyper guidance is starting point only" } ]; } }; function lightningSprintBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState10(trackState); const vc2 = opts.vehicleContext || {}; const wingless = isWingless2(vc2); const factory = PRIORS_BY_BUCKET10[bucket] || PRIORS_BY_BUCKET10.unknown; let list = factory(wingless).filter((p) => !skip.has(p.lever)); const hint = resolveLightningSprintChassisHint(vc2); if (hint && !skip.has("chassis_mfr")) { list = [{ lever: "chassis_mfr", action: hint.split(" \u2014 ")[0], reason: `public baseline (lightning): ${hint.includes(" \u2014 ") ? hint.split(" \u2014 ").slice(1).join(" \u2014 ") : hint}` }, ...list.filter((p) => p.lever !== "chassis" && p.lever !== "baseline")]; } else if (vc2.chassis_model_missing && !skip.has("chassis")) { const hasChassisPrior = list.some((p) => p.lever === "chassis"); if (!hasChassisPrior) { list.unshift({ lever: "chassis", action: "Add chassis mfr + model (Hyper, Saldana, etc.) in Garage \u2192 Setup \u2192 Chassis", reason: "public baseline (lightning): Hyper winged vs wingless PDFs differ \u2014 builder baseline when available" }); } } return list; } // scripts/lib/experimental/flatKartBaselinePriors.mjs function normalizeSurfaceState11(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var FLAT_KART_PUBLIC_BASELINE_CAVEAT = "Public baseline / starting point from available manufacturer & community knowledge \u2014 validate with your own A-B-A data."; var FLAT_KART_BASELINE = { source: "Flat Kart grassroots baseline (LO206/straight-rail dirt oval community norms)", cross_pct: "62-68", left_pct: "56-62", nose_pct: "42-48", rear_stagger_in: { tacky: "0.75-2", drying: "0.75-1.5", slick: "0.75-1.25", greasy: "1-2", unknown: "0.75-2" }, front_stagger_in: { tacky: "1-1.5", slick: "1-1.25", greasy: "1.25-1.5", unknown: "1-1.5" }, rear_track_in: "39-42", offset_note: "Straight-rail flat kart \u2014 cross %, left %, seat position, rear track width; not winged outlaw cage math" }; function isFlatKartProfile(vc2 = {}, cls = "", carType = "") { const c = String(cls || vc2.car_class || vc2.class_name || "").toLowerCase(); const ct = String(carType || vc2.car_type || "").toLowerCase(); if (/outlaw|cage kart|winged outlaw|f1 outlaw/.test(c) && !/flat kart/.test(c)) return false; if (/quarter.midget|qma|\bqm\b/.test(c)) return false; if (/micro sprint|600 micro|lightning sprint/.test(c)) return false; if (ct === "flatkart" || ct === "flat_kart" || ct === "kart_flat") return true; if (/lo206|\blo206\b|\b206 senior|\b206 junior|\b206 sportsman|restricted.*206|flat kart|straight.?rail|dirt oval kart|flat kart dirt|briggs flat|clone flat|yard kart dirt|sealed kart dirt/.test(c)) { return !/outlaw|winged|cage|wing kart/.test(c); } if ((ct === "kart" || ct === "sealed_kart") && /flat|lo206|\b206\b|briggs.*dirt|clone.*dirt|dirt oval kart/.test(c)) { return !/outlaw|winged|cage|wing/.test(c); } return false; } function resolveFlatKartChassisHint(vc2 = {}, trackState) { return resolveGrassrootsChassisHint("flat_kart", vc2, trackState); } var F = FLAT_KART_BASELINE; var PRIORS_BY_BUCKET11 = { tacky: [ { lever: "cross", action: `Scale and log cross weight (${F.cross_pct}% typical flat kart band) and left ${F.left_pct}% before chasing stagger`, reason: "public baseline (flat kart): straight-rail kart \u2014 cross and left run higher than winged outlaw; seat moves nose" }, { lever: "stagger", action: `Log rear stagger ${F.rear_stagger_in.tacky} in \xB7 front ${F.front_stagger_in.tacky} in \u2014 remeasure hot after psi stable`, reason: "public baseline (flat kart): moderate stagger vs micro \u2014 middle rotation after cross/left set" }, { lever: "seat", action: "Log seat position (hole/mm) with cross and nose % \u2014 seat forward fixes push on flat karts", reason: "public baseline (flat kart): nose via seat is primary scale lever on straight-rail" }, { lever: "rear_track", action: `Confirm rear track width (${F.rear_track_in} in typical dirt oval) \u2014 major flat-kart lever vs outlaw axle slot`, reason: "public baseline (flat kart): width in/out 2 mm steps \u2014 log with cross every session" }, { lever: "discipline", action: "One lever per run \u2014 cross, seat, rear track, and stagger interact on flat dirt oval karts", reason: "public baseline (flat kart): A-B-A logging replaces generic bands fastest" } ], drying: [ { lever: "stagger", action: `As track frees, take rear stagger toward ${F.rear_stagger_in.drying} in before cross add`, reason: "public baseline (flat kart): drying \u2014 stagger down often before more cross on straight-rail" }, { lever: "cross", action: "Re-check cross after stagger change \u2014 flat kart transfers weight differently as grip falls off", reason: "public baseline (flat kart): cross down 0.3\u20130.5% common on drying transition" } ], slick: [ { lever: "stagger", action: `Slick: reduce rear stagger toward ${F.rear_stagger_in.slick} in (1/8 in steps)`, reason: "public baseline (flat kart): less stagger tightens on dirt oval flat kart" }, { lever: "cross", action: "Slick: cross may need 0.5\u20131% down from tacky baseline (~58\u201364% band)", reason: "public baseline (flat kart): heavy cross + slick often binds off throttle" }, { lever: "seat", action: "If tight center after cross drop, try seat one hole before second cross change", reason: "public baseline (flat kart): seat splits entry vs center on straight-rail" } ], greasy: [ { lever: "cross", action: "Heavy/greasy: cross mid-band conservative \u2014 avoid max cross + max stagger on lap 1", reason: "public baseline (flat kart): moisture stacks grip quickly on flat karts" }, { lever: "stagger", action: `Greasy: rear stagger toward ${F.rear_stagger_in.greasy} in upper if loose after cross baseline`, reason: "public baseline (flat kart): more stagger can help loosen on heavy moisture" } ], unknown: [ { lever: "track_state", action: "Log tacky \u2192 drying \u2192 slick \u2014 flat kart priors stratify by surface like other grassroots classes", reason: "public baseline (flat kart): dirt oval flat kart setup is condition-specific" }, { lever: "cross", action: `Establish cross baseline (${F.cross_pct}%) and left ${F.left_pct}% on scales before hot laps`, reason: "public baseline (flat kart): higher cross than winged outlaw \u2014 validate on your scales" }, { lever: "chassis", action: "Log chassis brand in Garage (Laser, CRG, Prowler, Emmick, Triton, etc.) for brand notes over time", reason: "public baseline (flat kart): public brand detail is thin \u2014 generic flat patterns apply" } ] }; function flatKartBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState11(trackState); let list = (PRIORS_BY_BUCKET11[bucket] || PRIORS_BY_BUCKET11.unknown).filter((p) => !skip.has(p.lever)); const hint = resolveFlatKartChassisHint(opts.vehicleContext || {}, trackState); if (hint && !skip.has("chassis_mfr")) { list = [{ lever: "chassis_mfr", action: hint.split(" \u2014 ")[0], reason: `public baseline (flat kart): ${hint.includes(" \u2014 ") ? hint.split(" \u2014 ").slice(1).join(" \u2014 ") : hint}` }, ...list.filter((p) => p.lever !== "chassis")]; } else if (opts.vehicleContext?.chassis_model_missing && !skip.has("chassis")) { const hasChassisPrior = list.some((p) => p.lever === "chassis"); if (!hasChassisPrior) { list.unshift({ lever: "chassis", action: "Add chassis brand in Garage \u2192 Setup \u2192 Chassis (Laser, CRG, Prowler, etc.) for brand notes", reason: "public baseline (flat kart): generic flat dirt oval patterns apply \u2014 brand notes refine philosophy" }); } } return list; } // scripts/lib/experimental/grassrootsDirtOvalCommonPriors.mjs function normalizeSurfaceState12(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var GRASSROOTS_DIRT_OVAL_COMMON_CAVEAT = "High-value common principles across grassroots dirt oval classes \u2014 validate with your specific chassis and logged data."; var GRASSROOTS_COMMON_SOURCE = "Grassroots dirt oval common principles (cross-class)"; function isGrassrootsDirtOvalProfile(profile2) { return [ "quarter_midget", "micro_sprint_600", "micro_sprint_270", "junior_sprint", "micro_turf", "lightning_sprint", "outlaw_kart", "flat_kart", "hyper_midget" ].includes(profile2); } var PRIORS_BY_BUCKET12 = { tacky: [ { lever: "baseline_discipline", action: "Write down your baseline setup (psi, stagger, ride height, cross) \u2014 return to it when the car feels lost", reason: "grassroots common: every class wins by having a true baseline to reset to, not by random heat-to-heat changes" }, { lever: "squaring", action: "Square axles and time bearing carriers before ride height, stagger, or scaling \u2014 geometry first", reason: "grassroots common: 1/16 in square error shows on QM, micro, lightning, and outlaw tracks alike" }, { lever: "logging", action: "Log starting conditions each session: track state, cold/hot psi, stagger, and one driver feedback line", reason: "grassroots common: you cannot learn what worked if the starting point was not recorded" }, { lever: "scaling", action: "Scale with a consistent rule (driver in OR out every time) \u2014 log left %, rear %, and cross before chasing bite", reason: "grassroots common: weight distribution is a primary balance tool on small cars \u2014 ballast moves left/rear; shock collars mostly move cross" }, { lever: "direction_loosen", action: "General loosen moves on dirt: more rear stagger, higher RS tire psi, RR out, softer front / stiffer rear bars (one at a time)", reason: "grassroots common: directional logic is similar across grassroots classes \u2014 verify on your car with A-B-A" }, { lever: "direction_tighten", action: "General tighten moves on dirt: less stagger, lower RR psi, RR in, raise ride height slightly on small tracks (one at a time)", reason: "grassroots common: do not stack stagger + psi + cross in the same run \u2014 order matters for learning" }, { lever: "aba_discipline", action: "One lever per run \u2014 A-B-A confirm before the feature; same track state label on every log entry", reason: "grassroots common: A-B-A discipline is how thin data nights become real findings" } ], drying: [ { lever: "track_state", action: "Call the transition tacky \u2192 drying early \u2014 grassroots cars respond to surface change before big geometry moves", reason: "grassroots common: log the moment the track sheen breaks; priors stratify by grip" }, { lever: "stagger_direction", action: "Drying: usually take stagger out or ease RS psi before shock chasing", reason: "grassroots common: freeing tracks often want less rear stagger first across QM/micro/lightning/outlaw" }, { lever: "aba_discipline", action: "Re-log hot stagger and psi after the surface changes \u2014 compare to baseline sheet", reason: "grassroots common: drying nights punish teams that skip remeasurement" } ], slick: [ { lever: "stagger_direction", action: "Slick: less rear stagger and protect RR bite (psi / offset) before adding cross or bar", reason: "grassroots common: slick push often gets worse with max cross + max stagger stacked together" }, { lever: "direction_tighten", action: "Slick tight: lower RR psi, RR in, less stagger \u2014 cross down slightly if car binds on exit", reason: "grassroots common: small cars bind quickly on slick bullrings \u2014 small steps only" }, { lever: "baseline_discipline", action: "If lost on slick, return to baseline sheet then change ONE lever from the tighten list", reason: "grassroots common: baseline reset prevents compounding mistakes on feature night" } ], greasy: [ { lever: "logging", action: "Heavy/greasy: log whether the car is tight on entry or exit before any change", reason: "grassroots common: entry vs exit diagnosis picks the right first lever on wet tracks" }, { lever: "direction_loosen", action: "Greasy loose: may need more stagger or RS psi \u2014 still one lever per run", reason: "grassroots common: wet tracks often want more rear grip band before geometry extremes" }, { lever: "scaling", action: "Re-check cross after tire prep or psi change \u2014 wet nights shift effective weight", reason: "grassroots common: scaling mindset applies even when full scale pads are not available" } ], unknown: [ { lever: "baseline_discipline", action: "First night? Build a baseline sheet before custom tuning \u2014 every grassroots class rewards this", reason: "grassroots common: manufacturer numbers are starting points; your baseline is the real asset" }, { lever: "squaring", action: "Square and time the car once per rebuild \u2014 not every heat, but after any axle or carrier work", reason: "grassroots common: squaring is foundational across QM, micro, lightning, outlaw, and lower midget" }, { lever: "logging", action: "Log track state (tacky / drying / slick) on every DRK or setup note", reason: "grassroots common: recommendations and findings only make sense with surface context" }, { lever: "scaling", action: "Understand left %, rear %, and cross \u2014 know which moves need ballast vs which move cross only", reason: "grassroots common: scaling literacy pays off on every small-car class" }, { lever: "aba_discipline", action: "Plan A-B-A before the first change \u2014 grassroots learning speed is limited by discipline, not parts", reason: "grassroots common: one DOF per run is the highest-ROI habit on day one" } ] }; function grassrootsDirtOvalCommonPriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState12(trackState); const list = PRIORS_BY_BUCKET12[bucket] || PRIORS_BY_BUCKET12.unknown; return list.filter((p) => !skip.has(p.lever)); } function mergeGrassrootsCommonPriors(classPriors, trackState, skipLevers = /* @__PURE__ */ new Set()) { const used = new Set(classPriors.map((p) => p.lever)); const skip = /* @__PURE__ */ new Set([...skipLevers, ...used]); return grassrootsDirtOvalCommonPriors(trackState, { skipLevers: skip }); } // scripts/lib/experimental/grassrootsDayOneGuidance.mjs function isGrassrootsDayOneProfile(profile2) { return [ "quarter_midget", "micro_sprint_600", "micro_sprint_270", "junior_sprint", "micro_turf", "lightning_sprint", "outlaw_kart", "flat_kart" ].includes(profile2); } var GRASSROOTS_DAY_ONE_CAVEAT = "General best practices for grassroots dirt oval racing \u2014 not a substitute for your chassis manufacturer setup sheet or logged A-B-A data."; var GRASSROOTS_DAY_ONE_SOURCE = "Grassroots dirt oval day-one tuning guide (process)"; function normalizeSurfaceState13(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var PRIORS_BY_BUCKET13 = { tacky: [ { lever: "pre_run_log", action: "Before any change: log cold psi (all 4), hot stagger, ride heights, shock clickers/valving, track state, and lap-time notes", reason: "day-one process: you need a starting snapshot \u2014 parents and drivers both benefit from writing it down" }, { lever: "one_change", action: "Tonight's rule: ONE change per run \u2014 pick tire pressure, stagger, ride height, or one shock adj, then re-run", reason: "day-one process: A-B-A only works when you isolate the lever \u2014 stacking changes teaches nothing" }, { lever: "first_moves", action: "Common first adjustments on dirt: tire pressure (1 psi steps), stagger (1/4 in), small ride-height turn, then one shock click", reason: "day-one process: most QM/micro/lightning/outlaw cars respond to these before panhard, cross, or major geometry" }, { lever: "new_track", action: "New or unfamiliar track? Baseline sheet \u2192 3 clean laps \u2192 log tacky/slick feel \u2192 at most one small change before feature", reason: "day-one process: learn the track first \u2014 hero changes on lap 2 usually make the night harder" }, { lever: "reset_baseline", action: "Car lost? Go back to your written baseline (psi, stagger, heights, cross) before trying something new", reason: "day-one process: resetting beats guessing \u2014 compare how far you drifted from baseline before the next move" } ], drying: [ { lever: "pre_run_log", action: "Track transitioning? Re-log hot stagger and psi before the next change \u2014 drying shifts the whole window", reason: "day-one process: a move that worked in tacky may be wrong two heats later" }, { lever: "one_change", action: "Drying night: one lever per run \u2014 usually stagger or RR psi first, not shock + wing + cross together", reason: "day-one process: ordered changes help you learn what the track actually wanted" }, { lever: "first_moves", action: "First response while drying: often less stagger or slightly lower right-side psi \u2014 log before/after lap feel", reason: "day-one process: common grassroots pattern as rubber builds \u2014 confirm on your car" } ], slick: [ { lever: "pre_run_log", action: "Slick: log whether the car is tight on entry, middle, or exit before picking a lever \u2014 one sentence in the notes", reason: "day-one process: entry vs exit picks tire psi vs stagger vs ride height \u2014 logging the symptom saves time" }, { lever: "one_change", action: "Slick feature prep: protect RR bite \u2014 one change only (often RR psi down or stagger out 1/4 in)", reason: "day-one process: slick bullrings punish stacked aggressive changes" }, { lever: "reset_baseline", action: "Spinning or pushing everywhere? Baseline reset, then ONE tighten or loosen move from your sheet's short list", reason: "day-one process: slick frustration usually means too many unlogged changes \u2014 simplify" } ], greasy: [ { lever: "pre_run_log", action: "Heavy track: log entry push vs exit loose before changes \u2014 wet tracks confuse if you skip this step", reason: "day-one process: symptom logging keeps parents and drivers aligned on what to fix" }, { lever: "first_moves", action: "Greasy first tries: slightly more rear stagger or right-side psi before shock chasing \u2014 still one at a time", reason: "day-one process: common grassroots wet-track starting moves across small-car classes" }, { lever: "one_change", action: "One lever per heat on a wet night \u2014 write what you changed on the setup sheet or phone note", reason: "day-one process: handwriting the change builds the habit that makes later nights easier" } ], unknown: [ { lever: "pre_run_log", action: "First time with Crew Chief? Log psi, stagger, ride heights, shocks, track state, and lap notes before changing anything", reason: "day-one process: the system gets smarter when your starting point is recorded" }, { lever: "one_change", action: "A-B-A mindset: baseline run \u2192 one change \u2192 confirm run \u2014 that is how thin data becomes real findings", reason: "day-one process: you do not need a big notebook \u2014 one lever per run is enough to learn fast" }, { lever: "first_moves", action: "When unsure, start with tire pressure or stagger \u2014 log hot numbers after laps 8\u201310 on dirt", reason: "day-one process: safest high-value first moves for QM, outlaw, micro, and lightning classes" }, { lever: "new_track", action: "New track checklist: baseline \u2192 observe line \u2192 log track state \u2192 one small adjustment \u2192 re-evaluate", reason: "day-one process: a usable game plan beats chasing random advice in the pits" }, { lever: "reset_baseline", action: "If the car feels worse than heat 1, return to baseline \u2014 you are allowed to undo the experiment", reason: "day-one process: good handlers reset early instead of compounding mistakes into the feature" } ] }; function grassrootsDayOnePriors(trackState, opts = {}) { if (opts.profile && !isGrassrootsDayOneProfile(opts.profile)) return []; const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState13(trackState); const list = PRIORS_BY_BUCKET13[bucket] || PRIORS_BY_BUCKET13.unknown; return list.filter((p) => !skip.has(p.lever)); } // scripts/lib/experimental/grassrootsSurfaceAdjustments.mjs var GRASSROOTS_SURFACE_CAVEAT = "Directional guidance for this track condition only \u2014 adjust from your baseline in small steps; validate with A-B-A on your car."; var GRASSROOTS_SURFACE_SOURCE = "Grassroots dirt oval surface adjustment guide (directional)"; function normalizeGrassrootsSurface(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } function surfaceBucketLabel(bucket) { return { tacky: "TACKY", slick: "SLICK", drying: "DRYING", greasy: "GREASY", unknown: "SURFACE" }[bucket] || "SURFACE"; } var QM = { tacky: [ { lever: "surface_psi", action: "Tacky/normal grip: hold manufacturer baseline psi \u2014 remeasure RF/RR hot after laps 8\u201310 before changing", reason: "surface (QM): Stanley/RSR public bands assume normal grip; hot numbers pick the first move" }, { lever: "surface_stagger", action: "Tacky: log effective stagger after heat \u2014 QM spec tires change with pressure more than inch stagger on some brands", reason: "surface (QM): community norm is pressure-first on tacky before geometry chasing" } ], drying: [ { lever: "surface_psi", action: "Drying: ease RF/RR down 1 psi from tacky baseline before cross or camber moves", reason: "surface (QM): drying quarter midget tracks often want less right-side psi first (manufacturer + community pattern)" }, { lever: "surface_cross", action: "Drying: re-check cross after psi or stagger change \u2014 offset QM chassis transfers weight as grip falls", reason: "surface (QM): scaling principles still apply; less grip may need less cross built in" }, { lever: "surface_ride_height", action: "Drying entry push: small ride-height tweak per your brand sheet before camber chase", reason: "surface (QM): Bullrider/Stanley/NC ride-height references differ \u2014 stay on your baseline sheet" } ], slick: [ { lever: "surface_psi", action: "Slick: ease RF/RR toward lower band \u2014 protect LF/LR from over-drop (affects effective stagger)", reason: "surface (QM): community slick bands vary by tire \u2014 log hot pyrometer when available" }, { lever: "surface_cross", action: "Slick tighten: cross down ~0.5\u20131% from tacky baseline (X-method only \u2014 ballast for left/rear targets)", reason: "surface (QM): heavy cross on slick often binds on exit for light QM cars" } ], greasy: [ { lever: "surface_psi", action: "Greasy/heavy: may run upper right-side psi band \u2014 still 1 psi per change from baseline", reason: "surface (QM): more moisture often wants more RS psi before geometry on dirt/pavement QM" }, { lever: "surface_cross", action: "Greasy: avoid max cross + max camber on first laps \u2014 start from manufacturer baseline then one lever", reason: "surface (QM): light cars get reactive on heavy tracks; conservative first moves" } ] }; var OUTLAW = { tacky: [ { lever: "surface_stagger", action: "Tacky: log rear inch stagger hot (typical 1.5\u20133.5 in on 1/8 \u2014 scale for track size) before wing or cross", reason: "surface (outlaw): Slack/community baseline \u2014 stagger before wing on normal grip" }, { lever: "surface_wing", action: "Tacky: set wing to baseline sheet \u2014 outdoor offset karts often carry more wing in heavy qual than feature", reason: "surface (outlaw): manufacturer guidance separates qual vs feature wing planning" } ], drying: [ { lever: "surface_stagger", action: "Drying: take rear stagger toward 1.25\u20133 in band before adding wing", reason: "surface (outlaw): freeing offset kart tracks usually want less stagger before more wing" }, { lever: "surface_wing", action: "Drying: plan a wing step for feature \u2014 often less wing than heavy qual as rubber builds", reason: "surface (outlaw): outdoor tracks evolve quickly; log sheen break before booking wing" }, { lever: "surface_rr", action: "Drying: watch RR tread growth/temp \u2014 may outrun cross fixes for entry push", reason: "surface (outlaw): tread RR can over-grow on drying tracks before geometry fixes push" } ], slick: [ { lever: "surface_stagger", action: "Slick: reduce rear stagger toward 1\u20132.5 in (1/8 in steps from tacky baseline)", reason: "surface (outlaw): community slick sequence \u2014 stagger down before cross or wing stack" }, { lever: "surface_cross", action: "Slick: cross may need 0.3\u20130.5% down from tacky on tread RR", reason: "surface (outlaw): heavy cross + slick often binds off throttle on offset karts" } ], greasy: [ { lever: "surface_cross", action: "Greasy: cross mid-band \u2014 avoid max cross + max wing on lap 1", reason: "surface (outlaw): wet tracks punish stacked aggressive geometry" }, { lever: "surface_stagger", action: "Greasy loose: rear stagger toward upper band (2\u20134 in) if car needs rear grip", reason: "surface (outlaw): heavy moisture may want more rear stagger before shock chasing" } ] }; var MICRO600 = { tacky: [ { lever: "surface_stagger", action: "Tacky: rear stagger 5\u20138.5 in band (Hyper 600 guide) \u2014 remeasure hot after laps 8\u201310", reason: "surface (600 micro): Hyper public baseline stratifies by grip \u2014 tacky carries wider stagger band" }, { lever: "surface_psi", action: "Tacky: hold Hyper LF ~9 / RF-RR ~10\u201311 psi starting band \u2014 log hot before first change", reason: "surface (600 micro): Hyper Racing public guide \u2014 pressure before bar changes on normal grip" } ], drying: [ { lever: "surface_stagger", action: "Drying: take 1/4 in stagger out (toward 4.5\u20137 in) before shock chasing", reason: "surface (600 micro): Hyper drying sequence \u2014 less stagger first as rubber builds" }, { lever: "surface_psi", action: "Drying: ease RR psi down 1 psi steps toward lower RR band", reason: "surface (600 micro): Hyper tighten-on-freeing pattern \u2014 RR psi before panhard" }, { lever: "surface_wing", action: "Drying entry push: move wing back 1 hole before bar changes (Hyper winged normal 1/6\u20131/8)", reason: "surface (600 micro): Hyper ordered moves on small dirt tracks" } ], slick: [ { lever: "surface_stagger", action: "Slick tighten: reduce stagger toward 4\u20137 in (1/4 in steps; smaller LR roll-out per Hyper)", reason: "surface (600 micro): Hyper lists less stagger as first slick tighten move" }, { lever: "surface_rr", action: "Slick: RR in + lower RR psi before bar changes \u2014 do not stack with stagger same run", reason: "surface (600 micro): Hyper primary slick tighten pair \u2014 one lever per run" }, { lever: "surface_wing", action: "Slick tight: wing back; slick loose: wing forward (~28\xB0 small track reference)", reason: "surface (600 micro): Hyper wing direction on dirt \u2014 validate on your track" } ], greasy: [ { lever: "surface_stagger", action: "Greasy: upper stagger band 5\u20139 in \u2014 1/4 in steps from baseline only", reason: "surface (600 micro): Hyper heavy-track band before shock work" }, { lever: "surface_psi", action: "Greasy: higher psi vs slick on right side \u2014 still 1 psi per change", reason: "surface (600 micro): Hyper \u2014 tacky/wet usually carries more psi than slick" } ] }; var LIGHTNING = { tacky: [ { lever: "surface_stagger", action: 'Tacky: rear stagger ~5\u201310 in on 13" tires \u2014 remeasure hot after laps 8\u201310', reason: 'surface (lightning): Hyper Lightning guide \u2014 13" wheel band differs from 600 micro' }, { lever: "surface_psi", action: "Tacky: Hyper LF ~9 / RF-RR ~10\u201311 psi reference \u2014 log hot before geometry", reason: "surface (lightning): chassis-specific setup sheet still drives baseline numbers" } ], drying: [ { lever: "surface_stagger", action: "Drying: 1/4 in stagger out (toward 5\u20138 in) before shock chasing", reason: 'surface (lightning): Hyper freeing-track sequence on 13" dirt tires' }, { lever: "surface_psi", action: "Drying: RR psi down 1 psi steps \u2014 primary Hyper tighten move as track slicks", reason: "surface (lightning): ordered Hyper moves \u2014 psi before Jacobs ladder or wing" }, { lever: "surface_jacobs", action: "Drying/wingless: Jacobs ladder toward right hole if tight; winged \u2014 wing back if still tight", reason: "surface (lightning): Hyper Lightning winged vs wingless balance tools differ" } ], slick: [ { lever: "surface_stagger", action: "Slick tighten: stagger toward 4\u20137 in (smaller LR roll-out) \u2014 Hyper first slick move", reason: "surface (lightning): less stagger before bars on slick bullrings" }, { lever: "surface_rr", action: "Slick: RR in + lower RR psi before bar changes", reason: 'surface (lightning): Hyper slick tighten order on 13" wheels' }, { lever: "surface_ride_height", action: "Slick loose exit: lower ride heights 2\u20138 turns front / 1\u20133 rear per Hyper loosen list", reason: "surface (lightning): small steps only \u2014 one lever per run" } ], greasy: [ { lever: "surface_stagger", action: "Greasy: upper stagger band 5\u201310 in \u2014 1/4 in steps from baseline", reason: 'surface (lightning): Hyper heavy-track stagger band on 13" tires' }, { lever: "surface_psi", action: "Greasy: more right-side psi vs slick \u2014 log after each session", reason: "surface (lightning): Hyper \u2014 wet/tacky carries more psi than slick" } ] }; var PROFILE_ADJUSTMENTS = { quarter_midget: QM, outlaw_kart: OUTLAW, flat_kart: { tacky: [ { lever: "surface_cross", action: "Tacky: log cross/left/nose on scales before stagger or rear track width \u2014 flat kart cross runs higher than winged outlaw", reason: "surface (flat kart): straight-rail scaling \u2014 cross ~62\u201368% band on normal grip" } ], drying: [ { lever: "surface_stagger", action: "Drying: take rear stagger down 1/8 in before cross add \u2014 flat kart transition pattern", reason: "surface (flat kart): freeing dirt oval \u2014 stagger before second cross washer" }, { lever: "surface_cross", action: "Drying: cross down 0.3\u20130.5% from tacky after stagger change", reason: "surface (flat kart): less grip often wants less cross built in" } ], slick: [ { lever: "surface_stagger", action: "Slick: reduce rear stagger toward 3/4\u20131.25 in (1/8 in steps)", reason: "surface (flat kart): community slick sequence \u2014 stagger down before cross stack" }, { lever: "surface_cross", action: "Slick: cross down ~0.5\u20131% from tacky (~58\u201364% band)", reason: "surface (flat kart): heavy cross + slick binds exit on straight-rail" } ], greasy: [ { lever: "surface_cross", action: "Greasy: cross mid-band conservative \u2014 avoid max cross + max stagger lap 1", reason: "surface (flat kart): wet tracks stack grip quickly on flat karts" } ] }, micro_sprint_600: MICRO600, lightning_sprint: LIGHTNING }; function grassrootsSurfaceAdjustments(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) return []; const bucket = normalizeGrassrootsSurface(trackState); if (bucket === "unknown") return []; const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const list = PROFILE_ADJUSTMENTS[profile2]?.[bucket] || []; return list.filter((p) => !skip.has(p.lever)); } // scripts/lib/experimental/grassrootsContextPersonalization.mjs var GRASSROOTS_PERSONALIZED_CAVEAT = "Based on your logged setup on file \u2014 confirm with A-B-A before booking a change."; var GRASSROOTS_CONTEXT_GAP_CAVEAT = "Optional \u2014 add this in Garage \u2192 Setup to sharpen advice. Baseline guidance still works without it."; var REF = { quarter_midget: { rf_psi: { low: 10, high: 11, label: "QM RF reference 10\u201311 psi" }, rr_psi: { low: 10, high: 11, label: "QM RR reference 10\u201311 psi" }, lf_psi: { low: 5, high: 6, label: "QM LF reference 5\u20136 psi" }, cross_pct: { low: 52, high: 54, label: "QM cross ~52\u201354%" } }, outlaw_kart: { stagger: { low: 1.5, high: 3.5, label: "Outlaw rear inch stagger ~1.5\u20133.5 in (1/8 scale)" } }, flat_kart: { cross_pct: { low: 62, high: 68, label: "Flat kart cross ~62\u201368% tacky" }, left_pct: { low: 56, high: 62, label: "Flat kart left ~56\u201362%" }, stagger: { low: 0.75, high: 2, label: "Flat kart rear stagger ~3/4\u20132 in" } }, micro_sprint_600: { lf_psi: { low: 9, high: 9, label: "Hyper 600 LF ~9 psi" }, rf_psi: { low: 10, high: 11, label: "Hyper 600 RF ~10\u201311 psi" }, stagger: { low: 5, high: 8.5, label: "Hyper 600 rear stagger 5\u20138.5 in tacky" } }, lightning_sprint: { lf_psi: { low: 9, high: 9, label: "Hyper Lightning LF ~9 psi" }, rf_psi: { low: 10, high: 11, label: "Hyper Lightning RF ~10\u201311 psi" }, stagger: { low: 5, high: 10, label: "Hyper Lightning rear stagger 5\u201310 in tacky" } } }; var SYMPTOM_FIRST_LEVER = { tacky: { push: "tire pressure or cross from baseline", loose: "stagger or right-side psi \u2014 one step only", tight: "one psi or 1/4 in stagger from logged baseline" }, drying: { push: "RF/RR psi down 1 psi before geometry", loose: "stagger out 1/4 in before wing", tight: "re-log hot stagger then one psi step" }, slick: { push: "RR psi down or stagger down from tacky baseline", loose: "ride height or wing \u2014 not both same run", tight: "cross down or RR in \u2014 log before/after" }, greasy: { push: "right-side psi up slightly from baseline", loose: "rear stagger up 1/8\u20131/4 in if rules allow", tight: "avoid max cross + max wing on lap 1" }, unknown: { push: "log entry push in notes \u2014 picks psi vs cross", loose: "log exit loose \u2014 picks stagger vs wing", tight: "return to baseline sheet first" } }; function num3(v) { const n = Number(v); return Number.isFinite(n) ? n : null; } function compareToBand(value, band) { if (value == null || !band) return null; if (value < band.low) return `below ${band.label}`; if (value > band.high) return `above ${band.label}`; return `within ${band.label}`; } function assessGrassrootsContext(enrichedVc = {}, trackState, profile2) { const m = enrichedVc.setup_measurements || {}; const measCount = Object.keys(m).filter((k) => m[k] != null && m[k] !== "").length; const hasChassis = !enrichedVc.chassis_model_missing && enrichedVc.chassis_routing_confidence !== "missing"; const bucket = normalizeGrassrootsSurface(trackState); const hasTrack = bucket !== "unknown"; const hasMods = Boolean( enrichedVc.geo_notes || enrichedVc.trait_notes || enrichedVc.user_mods ); const hasFeel = Boolean( enrichedVc.trait_entry || enrichedVc.trait_mid || enrichedVc.trait_exit ); const hasCoreMeas = measCount >= 2 || m.rf_psi != null && m.lf_psi != null || m.stagger != null; let score = 0; if (hasChassis) score += 35; if (hasCoreMeas) score += 30; if (hasTrack) score += 20; if (hasFeel) score += 10; if (hasMods) score += 5; return { profile: profile2, hasChassis, hasCoreMeas, hasTrack, hasFeel, hasMods, measCount, surfaceBucket: bucket, completeness: Math.min(100, score), chassisLabel: [enrichedVc.chassis_manufacturer, enrichedVc.chassis_model].filter(Boolean).join(" \xB7 ") || enrichedVc.chassis_name || null }; } function measurementInsightPriors(profile2, enrichedVc, trackState) { const bands = REF[profile2]; const m = enrichedVc.setup_measurements || {}; if (!bands || !m) return []; const priors = []; const bucket = normalizeGrassrootsSurface(trackState); const staggerBand2 = bucket === "slick" && bands.stagger ? { ...bands.stagger, low: Math.max(1, bands.stagger.low - 1.5), high: bands.stagger.high - 1, label: `${bands.stagger.label} (slick often lower)` } : bands.stagger; const checks = [ ["rf_psi", m.rf_psi, bands.rf_psi, "RF"], ["rr_psi", m.rr_psi, bands.rr_psi, "RR"], ["lf_psi", m.lf_psi, bands.lf_psi, "LF"], ["stagger", num3(m.stagger), staggerBand2, "rear stagger"] ]; for (const [, val, band, label] of checks) { const cmp = compareToBand(num3(val), band); if (!cmp) continue; priors.push({ lever: `your_${label.replace(/\s+/g, "_")}`, action: `Your ${label} (${val}) is ${cmp} \u2014 compare to tonight's ${surfaceBucketLabel(bucket)} surface guidance before changing`, reason: `personalized (${profile2}): your logged numbers anchor moves \u2014 class reference is directional only` }); } if (m.left_pct != null && m.rear_pct != null && bands.cross_pct) { const cross = num3(m.left_pct) + num3(m.rear_pct) - 100; const cmp = compareToBand(cross, bands.cross_pct); if (cmp) { priors.push({ lever: "your_cross", action: `Your cross ~${cross.toFixed(1)}% is ${cmp} \u2014 re-scale with ballast (X-method) before shock chasing`, reason: `personalized (${profile2}): left ${m.left_pct}% + rear ${m.rear_pct}% on file` }); } } if (m.ride_ht_f || m.ride_ht_r) { priors.push({ lever: "your_ride_height", action: `Ride height on file: F ${m.ride_ht_f || "?"}" / R ${m.ride_ht_r || "?"}" \u2014 remeasure the same way (with/without driver) after each change`, reason: `personalized (${profile2}): inconsistent ride-height reference breaks A-B-A comparisons` }); } return priors.slice(0, 3); } function symptomHintPrior(enrichedVc, trackState) { const bucket = normalizeGrassrootsSurface(trackState); const hints = SYMPTOM_FIRST_LEVER[bucket] || SYMPTOM_FIRST_LEVER.unknown; const entry = String(enrichedVc.trait_entry || "").toLowerCase(); const mid = String(enrichedVc.trait_mid || "").toLowerCase(); const exit = String(enrichedVc.trait_exit || "").toLowerCase(); let symptom = null; let lever = null; if (/push|tight|bind|flat/.test(entry)) { symptom = "entry push"; lever = hints.push; } else if (/loose|free|spin|fishtail/.test(exit)) { symptom = "exit loose"; lever = hints.loose; } else if (/tight|bind/.test(mid)) { symptom = "mid tight"; lever = hints.tight || hints.push; } else if (/loose|free/.test(mid)) { symptom = "mid loose"; lever = hints.loose; } if (!lever) return null; return { lever: "your_symptom", action: `Car feel on file (${symptom}) on ${surfaceBucketLabel(bucket)}: try ${lever} first \u2014 one change, then re-run`, reason: "personalized: driver/parent notes steer the first lever on this surface" }; } function contextGapPrior(assessment, profile2) { if (!isGrassrootsDayOneProfile(profile2)) return null; if (!assessment.hasTrack) { return { lever: "ctx_track_state", action: "Set track state (tacky / drying / slick / greasy) in the logger \u2014 surface adjustments need it", reason: "context gap: class baseline works without it; surface + condition advice gets much sharper with track state" }; } if (!assessment.hasChassis) { const labels = { quarter_midget: "Bullrider, Stanley/RSR, or NC", outlaw_kart: "Slack, Toigo, Phantom, QRC, or Ultramax", flat_kart: "Laser, CRG, Prowler, Emmick, Triton, Coyote, or OTK", micro_sprint_600: "Hyper X-series (or your builder)", lightning_sprint: "Hyper Lightning or Saldana" }; return { lever: "ctx_chassis", action: `Add chassis mfr + model in Garage \u2192 Setup \u2192 Chassis (${labels[profile2] || "your builder"})`, reason: "context gap: manufacturer baseline sheet routes first \u2014 class-only advice is still available" }; } if (!assessment.hasCoreMeas) { return { lever: "ctx_measurements", action: "Log at least tire psi or stagger on your setup sheet \u2014 Crew Chief uses it as tonight's A-B-A baseline", reason: "context gap: two numbers (psi + stagger) make recommendations about YOUR car, not generic class norms" }; } if (!assessment.hasFeel) { return { lever: "ctx_driver_feel", action: "Optional: note entry/mid/exit feel (push, loose, tight) in Setup \u2014 helps pick the first lever faster", reason: "context gap: not required \u2014 adds symptom-aware first moves when you have a handling note" }; } return null; } function setupStylePrior(enrichedVc, profile2) { const style = enrichedVc.setup_style || enrichedVc.front_susp; if (!style) return null; const s = String(style).toLowerCase(); if (profile2 === "quarter_midget") return null; let action = null; if (/torsion|torsion bar|tb/.test(s)) { action = "Torsion front on file \u2014 ride height and bar turn affect cross; log bar position with psi changes"; } else if (/coil|a-arm|a arm/.test(s)) { action = "Coil/A-arm front on file \u2014 spring collar moves cross; match shock changes to spring rate"; } else if (/straight|solid/.test(s)) { action = "Straight-axle front on file \u2014 panhard/J-bar and RF wedge often beat shock chasing on dirt"; } if (!action) return null; return { lever: "your_setup_style", action, reason: `personalized (${profile2}): front suspension type changes which levers respond first` }; } function grassrootsContextPersonalizationPriors(profile2, vc2, trackState, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { priors: [], gapPriors: [], assessment: null }; } const enriched = enrichGrassrootsVehicleContext(vc2, profile2); const assessment = assessGrassrootsContext(enriched, trackState, profile2); const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const priors = []; for (const p of measurementInsightPriors(profile2, enriched, trackState)) { if (!skip.has(p.lever)) priors.push(p); } const style = setupStylePrior(enriched, profile2); if (style && !skip.has(style.lever)) priors.push(style); const symptom = symptomHintPrior(enriched, trackState); if (symptom && !skip.has(symptom.lever)) priors.push(symptom); const gapPriors = []; const gap = contextGapPrior(assessment, profile2); if (gap && !skip.has(gap.lever)) gapPriors.push(gap); return { priors, gapPriors, assessment, enrichedVc: enriched }; } function buildGrassrootsCarSummaryLine(enrichedVc, assessment) { if (!assessment || assessment.completeness < 25) return null; const parts = []; if (assessment.chassisLabel) parts.push(assessment.chassisLabel); const m = enrichedVc.setup_measurements || {}; if (m.rf_psi != null || m.lf_psi != null) { parts.push(`psi LF ${m.lf_psi ?? "?"}/RF ${m.rf_psi ?? "?"}`); } if (m.stagger != null) parts.push(`stagger ${m.stagger}"`); if (m.left_pct != null && m.rear_pct != null) { parts.push(`cross ~${(num3(m.left_pct) + num3(m.rear_pct) - 100).toFixed(1)}%`); } if (!parts.length) return null; return `Your car: ${parts.join(" \xB7 ")}`; } function buildGrassrootsContextGapLine(assessment) { if (!assessment || assessment.completeness >= 85) return null; if (!assessment.hasChassis) return "Sharpen advice: add chassis mfr + model in Garage \u2192 Setup \u2192 Chassis."; if (!assessment.hasCoreMeas) return "Sharpen advice: log tire psi or stagger on your setup sheet."; if (!assessment.hasTrack) return "Sharpen advice: set track state (tacky / slick / drying) in the logger."; if (!assessment.hasFeel) return "Optional: note entry/mid/exit feel in Setup for symptom-aware first moves."; return null; } // scripts/lib/experimental/grassrootsFirstMoveGuardrails.mjs var GRASSROOTS_GUARDRAILS_CAVEAT = "Supportive guardrails for newer handlers \u2014 validate every move with A-B-A on your car."; var GRASSROOTS_FIRST_MOVE_SOURCE = "Grassroots dirt oval first-adjustment guide"; var GRASSROOTS_GUARDRAIL_SOURCE = "Grassroots dirt oval common-mistake guardrails"; function normalizeSurfaceState14(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var SHARED_GUARDRAILS = [ { lever: "guard_stack_changes", action: "Avoid stacking changes \u2014 never adjust psi, stagger, cross, and shocks in the same run", reason: "guardrail: the #1 grassroots mistake is unlogged combo moves \u2014 you cannot learn what worked" }, { lever: "guard_symptom_first", action: "Avoid guessing direction \u2014 write entry push vs exit loose before turning a knob", reason: "guardrail: wrong-direction changes (tightening when the car needs stagger out) waste a heat" }, { lever: "guard_abandon_baseline", action: "Avoid abandoning baseline too fast \u2014 two confusing heats? Reset to your sheet, then one deliberate move", reason: "guardrail: good handlers undo experiments early instead of compounding into the feature" }, { lever: "guard_skip_square", action: "Avoid chasing stagger or psi if the axle is not squared \u2014 1/16 in error mimics a bad shock", reason: "guardrail: squaring is free speed on QM, micro, lightning, and outlaw cars" }, { lever: "guard_shock_before_basics", action: "Avoid shock-chasing before tire psi and stagger \u2014 shocks fine-tune, they rarely fix a wrong baseline", reason: "guardrail: parents and drivers often go to shocks first; psi/stagger usually come first on dirt" } ]; var FIRST_MOVES_BY_PROFILE = { quarter_midget: { tacky: [ { lever: "move_psi_first", action: "Smart first move: RF/RR psi in 1 psi steps from your manufacturer baseline \u2014 log hot after laps 8\u201310", reason: "first move (QM): Stanley/Bullrider/NC sheets start with pressure before geometry on normal grip" }, { lever: "move_stagger_second", action: "Second try (separate run): effective stagger via psi or inch stagger \u2014 never both same heat", reason: "first move (QM): QMA tires change effective stagger with pressure on many setups" } ], drying: [ { lever: "move_psi_first", action: "Smart first move while drying: RF/RR down 1 psi from tacky baseline before cross or camber", reason: "first move (QM): drying tracks usually want less right-side psi first" } ], slick: [ { lever: "move_psi_first", action: "Smart first move on slick: ease RF/RR toward lower band \u2014 one psi per run, protect LF/LR", reason: "first move (QM): slick push often responds to right-side psi before cross changes" }, { lever: "move_cross_second", action: "Second try: cross down ~0.5\u20131% from tacky (X-method) \u2014 not same run as psi", reason: "first move (QM): heavy cross binds exit on slick bullrings for light cars" } ], greasy: [ { lever: "move_psi_first", action: "Smart first move on heavy track: slight RS psi up from baseline \u2014 still 1 psi per change", reason: "first move (QM): greasy often wants more right-side pressure before geometry" } ], unknown: [ { lever: "move_psi_first", action: "Smart first move when unsure: tire pressure (1 psi steps) \u2014 log before and after lap feel", reason: "first move (QM): safest high-value starting lever for quarter midgets" } ] }, outlaw_kart: { tacky: [ { lever: "move_stagger_first", action: "Smart first move: log hot rear inch stagger before wing or cross \u2014 scale for track size", reason: "first move (outlaw): offset karts usually respond to stagger before wing on tacky" }, { lever: "move_wing_second", action: "Second try: one wing step \u2014 qual vs feature plan before stacking with cross", reason: "first move (outlaw): outdoor offset tracks often carry more wing early than feature" } ], drying: [ { lever: "move_stagger_first", action: "Smart first move while drying: stagger down 1/8 in before adding wing", reason: "first move (outlaw): freeing tracks want less rear stagger before more downforce" } ], slick: [ { lever: "move_stagger_first", action: "Smart first move on slick: rear stagger down 1/8 in steps from tacky baseline", reason: "first move (outlaw): slick offset karts usually need less rear stagger before cross" } ], greasy: [ { lever: "move_cross_careful", action: "Smart first move on wet: cross mid-band \u2014 avoid max cross + max wing on lap 1", reason: "first move (outlaw): greasy tracks punish stacked aggressive geometry" } ], unknown: [ { lever: "move_stagger_first", action: "Smart first move when unsure: rear stagger \u2014 log hot, one step per run", reason: "first move (outlaw): inch stagger is the primary balance tool on offset outlaws" } ] }, flat_kart: { tacky: [ { lever: "move_cross_first", action: "Smart first move: log cross/left/nose on scales \u2014 flat kart cross ~62\u201368% before rear track or stagger chase", reason: "first move (flat kart): straight-rail scaling foundation on tacky" }, { lever: "move_psi_second", action: "Second try: hot psi lap 8\u201310 on sealed tread \u2014 1/4 psi step, separate run from cross", reason: "first move (flat kart): psi supports cross family on LO206 dirt oval" } ], drying: [ { lever: "move_stagger_first", action: "Smart first move while drying: stagger down 1/8 in before cross add", reason: "first move (flat kart): freeing dirt oval \u2014 stagger before cross washers" } ], slick: [ { lever: "move_stagger_first", action: "Smart first move on slick: rear stagger down 1/8 in from tacky baseline", reason: "first move (flat kart): slick flat kart \u2014 less stagger before cross" }, { lever: "move_cross_second", action: "Second try: cross down 0.3\u20130.5% \u2014 not same run as stagger", reason: "first move (flat kart): heavy cross binds exit on slick straight-rail" } ], greasy: [ { lever: "move_cross_careful", action: "Smart first move on wet: conservative cross mid-band \u2014 seat/rear track before washer cross", reason: "first move (flat kart): wet stacks grip on flat karts" } ], unknown: [ { lever: "move_cross_first", action: "Smart first move when unsure: establish scaled baseline (cross/left/nose) \u2014 then one lever per A-B-A run", reason: "first move (flat kart): your log sheet beats generic chart fastest" } ] }, micro_sprint_600: { tacky: [ { lever: "move_psi_first", action: "Smart first move: log hot psi (Hyper LF ~9 / RF-RR ~10\u201311 band) \u2014 1 psi steps only", reason: "first move (600 micro): Hyper public guide \u2014 pressure before bars or panhard" }, { lever: "move_stagger_second", action: "Second try: rear stagger 1/4 in step from baseline \u2014 remeasure after laps 8\u201310", reason: 'first move (600 micro): tacky band ~5\u20138.5 in on 10" wheels' } ], drying: [ { lever: "move_stagger_first", action: "Smart first move while drying: 1/4 in stagger out before shock chasing", reason: "first move (600 micro): Hyper drying sequence \u2014 stagger before valving" } ], slick: [ { lever: "move_stagger_first", action: "Smart first move on slick: stagger down 1/4 in \u2014 smaller LR roll-out per Hyper", reason: "first move (600 micro): less stagger is first slick tighten move" }, { lever: "move_rr_psi_second", action: "Second try: RR psi down 1 psi \u2014 do not stack with stagger same run", reason: "first move (600 micro): RR in + lower psi pair \u2014 one at a time" } ], greasy: [ { lever: "move_stagger_first", action: "Smart first move on heavy: upper stagger band \u2014 1/4 in from baseline only", reason: "first move (600 micro): wet tracks may want more rear stagger before shocks" } ], unknown: [ { lever: "move_psi_stagger", action: "Smart first move when unsure: psi or stagger \u2014 Hyper order, one lever per run", reason: "first move (600 micro): safest starting pair on Hyper-style micros" } ] }, lightning_sprint: { tacky: [ { lever: "move_psi_first", action: 'Smart first move: hot psi on 13" tires (Hyper LF ~9 / RF-RR ~10\u201311) \u2014 1 psi steps', reason: "first move (lightning): not 600 micro geometry \u2014 log on Lightning sheet" }, { lever: "move_stagger_second", action: 'Second try: rear stagger 1/4 in on 13" band (~5\u201310 in tacky) \u2014 one run only', reason: "first move (lightning): remeasure hot after laps 8\u201310" } ], drying: [ { lever: "move_stagger_first", action: "Smart first move while drying: stagger out 1/4 in before Jacobs ladder or wing", reason: 'first move (lightning): Hyper freeing-track order on 13" dirt' } ], slick: [ { lever: "move_stagger_first", action: "Smart first move on slick: stagger toward 4\u20137 in band \u2014 Hyper first tighten move", reason: "first move (lightning): less stagger before bar changes on slick bullrings" } ], greasy: [ { lever: "move_psi_first", action: "Smart first move on heavy: right-side psi up slightly vs slick \u2014 log each session", reason: 'first move (lightning): tacky/wet carries more psi than slick on 13" tires' } ], unknown: [ { lever: "move_psi_stagger", action: "Smart first move when unsure: psi then stagger \u2014 winged vs wingless sheet differs", reason: "first move (lightning): validate on Hyper Lightning PDF for your config" } ] } }; var GUARDRAIL_PICK_BY_BUCKET = { slick: ["guard_stack_changes", "guard_symptom_first", "guard_abandon_baseline"], drying: ["guard_stack_changes", "guard_symptom_first", "guard_shock_before_basics"], tacky: ["guard_stack_changes", "guard_skip_square", "guard_shock_before_basics"], greasy: ["guard_stack_changes", "guard_symptom_first", "guard_abandon_baseline"], unknown: ["guard_stack_changes", "guard_symptom_first", "guard_abandon_baseline", "guard_skip_square"] }; function grassrootsFirstMoveGuardrails(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { firstMovePriors: [], guardrailPriors: [] }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState14(trackState); const maxFirst = opts.maxFirst ?? 2; const maxGuard = opts.maxGuard ?? 2; const firstList = FIRST_MOVES_BY_PROFILE[profile2]?.[bucket] || FIRST_MOVES_BY_PROFILE[profile2]?.unknown || []; const firstMovePriors = firstList.filter((p) => !skip.has(p.lever)).slice(0, maxFirst); const pick = GUARDRAIL_PICK_BY_BUCKET[bucket] || GUARDRAIL_PICK_BY_BUCKET.unknown; const guardrailPriors = SHARED_GUARDRAILS.filter((p) => pick.includes(p.lever) && !skip.has(p.lever)).slice(0, maxGuard); return { firstMovePriors, guardrailPriors }; } // scripts/lib/experimental/grassrootsTirePressureGuidance.mjs var GRASSROOTS_TIRE_CAVEAT = "Directional tire pressure guidance from public manufacturer and community baselines \u2014 log cold/hot on YOUR car; A-B-A beats any chart."; var GRASSROOTS_TIRE_SOURCE = "Grassroots dirt oval tire pressure guide (directional)"; var START_BANDS = { quarter_midget: "RF 10\u201311 / RR 10\u201311 / LF 5\u20136 / LR 5\u20136 psi (Stanley/RSR public reference \u2014 spec QMA tires)", micro_sprint_600: 'LF 9 / RF 9 / LR 5\u20138 / RR 6.5\u201310 psi (Hyper 600 winged normal band \u2014 10" wheels)', lightning_sprint: 'LF 10 / RF 10 / LR 4\u201310 / RR 5\u201312 psi winged (Hyper 13" band \u2014 verify wingless sheet if applicable)', outlaw_kart: "LF 8\u201310 / RF 10\u201312 / LR 6\u20138 / RR 8\u201311 psi (offset outlaw community starting band \u2014 scale to your tire brand)", flat_kart: "LF 8\u201310 / RF 10\u201312 / LR 6\u20138 / RR 8\u201311 psi (LO206/flat dirt oval community band \u2014 hot log lap 8\u201310 on sealed tread)" }; var COND_BY_PROFILE = { quarter_midget: { tacky: { lever: "tire_guide_cond", action: "Tacky: hold manufacturer baseline psi \u2014 remeasure RF/RR hot after laps 8\u201310; first change is usually 1 psi on right side only", reason: "tire guide (QM): normal grip assumes full RS band; hot numbers pick direction before cross or camber" }, drying: { lever: "tire_guide_cond", action: "Drying: ease RF/RR down 1 psi from tacky baseline before cross or camber \u2014 protect LF/LR from big drops (changes effective stagger)", reason: "tire guide (QM): drying QM tracks usually want less right-side psi first (manufacturer + community pattern)" }, slick: { lever: "tire_guide_cond", action: "Slick: walk RF/RR toward lower band in 1 psi steps \u2014 do not crash LF/LR; if still tight after two psi runs, try stagger before more cross", reason: "tire guide (QM): slick often responds to RS psi before geometry; left-side over-drop mimics stagger loss" }, greasy: { lever: "tire_guide_cond", action: "Greasy/heavy: may run upper right-side psi band \u2014 still 1 psi per run from your logged baseline", reason: "tire guide (QM): moisture often wants more RS psi before geometry on dirt/pavement QM" }, unknown: { lever: "tire_guide_cond", action: "When unsure: start from manufacturer sheet band \u2014 log cold, then hot after laps 8\u201310 before any change", reason: "tire guide (QM): pressure-first is the safest high-value lever on quarter midgets" } }, outlaw_kart: { tacky: { lever: "tire_guide_cond", action: "Tacky: log all four corners cold and hot \u2014 on offset outlaws, inch stagger and cross usually lead psi unless RR tread is over-growing", reason: "tire guide (outlaw): Slack/community norm \u2014 stagger + cross before pressure chasing on normal grip" }, drying: { lever: "tire_guide_cond", action: "Drying: if RR tread grows or overheats, ease RR 1 psi before adding wing \u2014 stagger down often pairs with RR relief", reason: "tire guide (outlaw): tread RR can outrun cross fixes on freeing outdoor tracks" }, slick: { lever: "tire_guide_cond", action: "Slick: protect RR for drive \u2014 ease RR psi down 1 psi steps; stagger down usually comes before stacking cross + wing", reason: "tire guide (outlaw): slick offset karts need RR bite; lower RR psi is a common tighten move after stagger" }, greasy: { lever: "tire_guide_cond", action: "Greasy: slight RS psi up from baseline is OK \u2014 avoid max cross + max wing + max psi on lap 1", reason: "tire guide (outlaw): heavy tracks punish stacked aggressive geometry and pressure" }, unknown: { lever: "tire_guide_cond", action: "When unsure: log hot stagger first, then psi \u2014 offset outlaws balance stagger + cross before fine pressure work", reason: "tire guide (outlaw): inch stagger is the primary balance tool; psi supports it" } }, flat_kart: { tacky: { lever: "tire_guide_cond", action: "Tacky: log cold/hot psi lap 8\u201310 on sealed tread \u2014 cross/seat/rear track usually lead psi unless RS hot off band", reason: "tire guide (flat kart): LO206 dirt oval \u2014 psi supports cross/stagger family" }, drying: { lever: "tire_guide_cond", action: "Drying: ease RS down 1/4 psi before cross add \u2014 stagger down 1/8 in separate run", reason: "tire guide (flat kart): freeing track \u2014 psi/stagger before cross washers" }, slick: { lever: "tire_guide_cond", action: "Slick: RS toward lower band \u2014 cross down 0.5% after stagger out, not same heat as psi", reason: "tire guide (flat kart): slick straight-rail \u2014 RR bite before cross stack" }, greasy: { lever: "tire_guide_cond", action: "Greasy: RS upper band OK \u2014 conservative cross/stagger pairing first laps", reason: "tire guide (flat kart): wet stacks grip on flat karts quickly" }, unknown: { lever: "tire_guide_cond", action: "When unsure: hot psi log lap 8\u201310 then one 1/4 psi step \u2014 flat kart A-B-A replaces chart fast", reason: "tire guide (flat kart): pressure-first on sealed tread dirt oval" } }, micro_sprint_600: { tacky: { lever: "tire_guide_cond", action: "Tacky: Hyper band LF ~9 / RF\u2013RR ~10\u201311 \u2014 right-side stiffer loosens, left-side stiffer tightens; LR psi also shifts effective stagger", reason: "tire guide (600 micro): Hyper public guide \u2014 log hot before bar or panhard changes" }, drying: { lever: "tire_guide_cond", action: "Drying: ease RR psi down 1 psi as track frees \u2014 take stagger out 1/4 in in a separate run, not same heat", reason: "tire guide (600 micro): Hyper drying sequence \u2014 pressure and stagger, one lever per run" }, slick: { lever: "tire_guide_cond", action: "Slick: lower RR psi toward 5\u20136 band and protect RR tread \u2014 if still loose off, stagger before shock valving", reason: "tire guide (600 micro): Hyper tighten list \u2014 RR in + lower RR psi before bars on slick" }, greasy: { lever: "tire_guide_cond", action: 'Greasy: upper RS psi band is normal \u2014 1 psi steps only; wet often carries more psi than slick on 10" dirt', reason: "tire guide (600 micro): Hyper \u2014 tacky/wet psi bands sit above slick references" }, unknown: { lever: "tire_guide_cond", action: "When unsure: Hyper order \u2014 psi or stagger first, one lever per run on 600 micro", reason: "tire guide (600 micro): safest starting pair before wing or shock work" } }, lightning_sprint: { tacky: { lever: "tire_guide_cond", action: 'Tacky: Hyper 13" band LF ~10 / RF\u2013RR ~10\u201311 \u2014 log on Lightning sheet (winged vs wingless bands differ)', reason: "tire guide (lightning): not 600 micro geometry \u2014 validate wingless LR/RR if applicable" }, drying: { lever: "tire_guide_cond", action: "Drying: RR psi down 1 psi steps as sheen breaks \u2014 stagger out 1/4 in before Jacobs ladder or wing in same run", reason: 'tire guide (lightning): Hyper freeing-track order on 13" dirt' }, slick: { lever: "tire_guide_cond", action: "Slick: lower RR/LR psi toward Hyper slick tighten band \u2014 RR in offset before bar changes if still tight", reason: "tire guide (lightning): primary slick tighten moves per Hyper Lightning guide" }, greasy: { lever: "tire_guide_cond", action: "Greasy: run upper RS psi vs slick \u2014 still 1 psi per change; heavy tracks may want wider stagger band first", reason: 'tire guide (lightning): wet carries more psi than slick on 13" tires' }, unknown: { lever: "tire_guide_cond", action: "When unsure: psi then stagger on Hyper Lightning PDF \u2014 winged vs wingless sheet differs", reason: "tire guide (lightning): validate config before copying micro 600 numbers" } } }; var RR_MANAGE = { quarter_midget: { lever: "tire_guide_rr", action: "Protect RR on slick: do not over-drop RR psi \u2014 if drive-off fades, reset RR 1 psi up before chasing cross", reason: "tire guide (QM): light cars lose drive when RR is over-softened; RS psi changes affect effective stagger" }, outlaw_kart: { lever: "tire_guide_rr", action: "RR management: log tread duro and hot RR temp \u2014 if RR grows on drying track, ease psi or stagger before cross stack", reason: "tire guide (outlaw): tread RR compound is a rules lever; growth often beats geometry for entry push" }, flat_kart: { lever: "tire_guide_rr", action: "RR management (flat kart): log hot RR on sealed tread \u2014 if drive-off fades on slick, RR up 1/4 psi before cross chase", reason: "tire guide (flat kart): RS psi changes effective cross on straight-rail" }, micro_sprint_600: { lever: "tire_guide_rr", action: "Protect RR on slick: lower RR psi tightens but over-drop kills drive \u2014 Hyper pattern is 1 psi steps with hot remeasure", reason: "tire guide (600 micro): RR bite sets exit; pair RR psi moves with stagger, not same run" }, lightning_sprint: { lever: "tire_guide_rr", action: "Protect RR on freeing tracks: ease RR psi as track slicks but stop if lap time drops on drive-off \u2014 try RR in offset next", reason: "tire guide (lightning): Hyper slick sequence \u2014 RR psi then RR in before bar work" } }; var RAISE_LOWER = { tacky: { lever: "tire_guide_raise_lower", action: "Raise RS psi \u2192 usually loosens \xB7 Raise LS psi \u2192 usually tightens \xB7 Always 1 psi per run, log hot after laps 8\u201310", reason: "tire guide: conservative dirt oval cheat sheet \u2014 validate on your tire brand and scale" }, drying: { lever: "tire_guide_raise_lower", action: "Drying direction: lower RF/RR first (1 psi) \xB7 If exit loose after RS drop, stop psi \u2014 try stagger out instead", reason: "tire guide: freeing tracks want less RS spring; do not keep lowering if car gets loose" }, slick: { lever: "tire_guide_raise_lower", action: "Slick: lower RR psi tightens entry/mid \u2014 if still tight in center, stagger down before more RS psi drops", reason: "tire guide: psi helps until RR is over-soft; then stagger/geometry lead" }, greasy: { lever: "tire_guide_raise_lower", action: "Greasy: slight RS psi up can add grip \u2014 if car gets loose, undo psi before adding wing or cross", reason: "tire guide: heavy moisture often wants more RS pressure; one direction at a time" }, unknown: { lever: "tire_guide_raise_lower", action: "Rule of thumb: RS up loosens / LS up tightens on most grassroots dirt setups \u2014 log symptom before direction", reason: "tire guide: directional only \u2014 your logged A-B-A overrides this chart" } }; var PSI_PRIORITY = { quarter_midget: { lever: "tire_guide_priority", action: "Try psi first when: entry push on tacky/drying, or hot RF/RR outside your sheet band \u2014 try stagger/cross first when: exit loose after two psi runs or LF/LR already at band edge", reason: "tire guide (QM): pressure is the easiest first lever; geometry follows logged hot numbers" }, outlaw_kart: { lever: "tire_guide_priority", action: "Try stagger/cross first when: balance issue on tacky \u2014 try psi first when: RR tread growth, hot RR, or repeatable entry push after stagger is logged", reason: "tire guide (outlaw): offset karts are stagger-primary; psi fixes RR temp/growth and fine balance" }, micro_sprint_600: { lever: "tire_guide_priority", action: "Try psi/stagger first when: Hyper sheet band is off or track state changed \u2014 try shocks/wing first when: two psi+stagger runs failed and hot numbers look stable", reason: "tire guide (600 micro): Hyper ordered list \u2014 basics before valving" }, lightning_sprint: { lever: "tire_guide_priority", action: 'Try psi/stagger first when: 13" hot numbers drift from Hyper band \u2014 try wing/bars first when: psi and stagger are logged stable but balance unchanged', reason: 'tire guide (lightning): same Hyper discipline as micro but 13" sheet \u2014 not Maxim sprint logic' } }; var HOT_LOG = { lever: "tire_guide_hot", action: "Tire management: log cold psi before rollout, hot psi after laps 8\u201310 on all four corners \u2014 same gauge, same sequence every session", reason: "tire guide: hot numbers drive the next 1 psi step; cold-only guesses cause over-adjusting" }; function grassrootsTirePressureGuidance(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { tirePriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; const priors = []; const band = START_BANDS[profile2]; if (band && !skip.has("tire_pressure") && !skip.has("tire_guide_start")) { priors.push({ lever: "tire_guide_start", action: `Starting band (public baseline): ${band}`, reason: `tire guide (${profile2.replace(/_/g, " ")}): conservative manufacturer/community reference \u2014 not a mandate` }); } const cond = COND_BY_PROFILE[profile2]?.[bucket] || COND_BY_PROFILE[profile2]?.unknown; if (cond && !skip.has("tire_guide_cond") && !skip.has("surface_psi")) { priors.push(cond); } const rr = RR_MANAGE[profile2]; if (rr && !skip.has("tire_guide_rr")) priors.push(rr); const priority = PSI_PRIORITY[profile2]; if (priority && !skip.has("tire_guide_priority")) priors.push(priority); const raiseLower = RAISE_LOWER[bucket] || RAISE_LOWER.unknown; if (raiseLower && !skip.has("tire_guide_raise_lower")) priors.push(raiseLower); if (!skip.has("tire_guide_hot")) priors.push(HOT_LOG); const tirePriors = priors.slice(0, maxPriors); return { tirePriors, surfaceBucket: bucket }; } // scripts/lib/experimental/grassrootsScalingGuidance.mjs var GRASSROOTS_SCALING_CAVEAT = "Directional scaling guidance from public manufacturer and community baselines \u2014 same driver-in/out rule every time; logged A-B-A overrides any chart."; var GRASSROOTS_SCALING_SOURCE = "Grassroots dirt oval scaling & weight distribution guide (directional)"; var TARGET_BANDS = { quarter_midget: { lever: "scale_guide_targets", action: "Weight targets (public baseline): cross ~52\u201354% \xB7 left ~55\u201358% \xB7 rear ~55\u201358% \u2014 Stanley/RSR no-driver reference; X-method moves cross, ballast moves left/rear", reason: "scale guide (QM): manufacturer sheets differ \u2014 log YOUR repeatable baseline before chasing bite" }, outlaw_kart: { lever: "scale_guide_targets", action: "Weight targets (public baseline): cross ~59\u201366% \xB7 left ~52\u201358% \u2014 Slack offset cage band; log seat cradle mm with every cross change", reason: "scale guide (outlaw): offset chassis \u2014 cross % is primary balance, not symmetric LO206 width math" }, flat_kart: { lever: "scale_guide_targets", action: "Weight targets (flat kart community): cross ~62\u201368% \xB7 left ~56\u201362% \xB7 nose ~42\u201348% via seat \u2014 HIGHER cross/left than winged outlaw; rear track width major lever", reason: "scale guide (flat kart): straight-rail dirt oval \u2014 not outlaw cage or Hyper micro numbers" }, micro_sprint_600: { lever: "scale_guide_targets", action: 'Scaling baseline (Hyper 600): log left % + rear % on scales (driver-in, shocks unhooked on torsion) before cross chase \xB7 blocks ~1-1/2" all corners winged ref', reason: "scale guide (600 micro): Hyper optional scaling still needs consistent pad location each week" }, lightning_sprint: { lever: "scale_guide_targets", action: 'Ride-height baseline (Hyper 13" winged, no driver to torsion bar): LF 7-1/2 / RF 8-7/8 / LR 9-1/4 / RR 11-1/8 in \u2014 scale same driver rule; wingless sheet differs', reason: "scale guide (lightning): midget-scale Hyper dirt \u2014 not Maxim full sprint scaling logic" } }; var REPEAT_BASELINE = { lever: "scale_guide_repeat", action: "Establish one repeatable scaled baseline \u2014 same driver in OR out every time, same scale pads, log cross/left/rear (or ride heights) before hot laps; return here when the car feels lost", reason: "scale guide: scaling is foundational \u2014 good teams rescale after major ballast or seat moves, not every heat" }; var EFFECTS_BY_PROFILE = { quarter_midget: { lever: "scale_guide_effects", action: "Directional effects (QM): ballast left adds left % \xB7 shock collars mostly move cross only (X-method) \xB7 small ride-height change shifts nose \u2014 recheck RH after ballast", reason: "scale guide (QM): Stanley/RSR recheck ride height after scaling before trusting cross numbers" }, outlaw_kart: { lever: "scale_guide_effects", action: "Directional effects (outlaw): seat mm forward adds nose bite and cross feel \xB7 cross up often tightens mid/exit on offset cage \xB7 wing + cross stack quickly \u2014 one lever per run", reason: "scale guide (outlaw): Slack norm \u2014 seat + cross before tire prep chasing" }, flat_kart: { lever: "scale_guide_effects", action: "Directional effects (flat kart): seat forward adds nose \xB7 rear track in tightens \xB7 cross washers fine-tune after nose/left set \xB7 front stagger changes cross \u2014 log on YOUR scales", reason: "scale guide (flat kart): seat + rear track width before cross washer stacks" }, micro_sprint_600: { lever: "scale_guide_effects", action: "Directional effects (600 micro): block/torsion turns set ride height \u2014 raising front adds nose load \xB7 bar split changes cross feel \xB7 LR+RF weight can tighten mid/exit on small tracks", reason: "scale guide (600 micro): Hyper \u2014 geometry and scaling before shock valving" }, lightning_sprint: { lever: "scale_guide_effects", action: "Directional effects (lightning): ride height +1 RS / +2 LS tightens (Hyper dirt ref) \xB7 panhard ~4 in front affects roll \xB7 wing back tightens entry \u2014 not same run as cross", reason: 'scale guide (lightning): 13" Hyper scaling philosophy \u2014 stagger/psi before fine cross unless numbers drift' } }; var COND_BY_PROFILE2 = { quarter_midget: { tacky: { lever: "scale_guide_cond", action: "Tacky: hold scaled baseline cross/left \u2014 re-log after psi or stagger change (offset QM transfers weight as grip changes)", reason: "scale guide (QM): normal grip assumes manufacturer cross band until hot tire numbers say otherwise" }, drying: { lever: "scale_guide_cond", action: "Drying: re-check cross after RF/RR psi down \u2014 may need 0.3\u20130.5% cross drop before camber chase", reason: "scale guide (QM): less grip often wants less cross built into baseline" }, slick: { lever: "scale_guide_cond", action: "Slick: cross down ~0.5\u20131% from tacky (X-method only) \u2014 ballast for left/rear targets, not shock collars alone", reason: "scale guide (QM): heavy cross on slick binds exit on light QM cars" }, greasy: { lever: "scale_guide_cond", action: "Greasy: cross mid-band \u2014 avoid max cross + max camber on lap 1; rescale if seat or ballast moved", reason: "scale guide (QM): heavy tracks punish stacked aggressive geometry" }, unknown: { lever: "scale_guide_cond", action: "When unsure: return to last logged scaled baseline before new cross or ballast moves", reason: "scale guide (QM): scaling changes stick \u2014 log before/after on scales when available" } }, outlaw_kart: { tacky: { lever: "scale_guide_cond", action: "Tacky: log cross + seat mm before stagger or wing \u2014 offset outlaws scale cross first on normal grip", reason: "scale guide (outlaw): Slack baseline band before inch stagger chasing" }, drying: { lever: "scale_guide_cond", action: "Drying: re-check cross after stagger down \u2014 may need 0.3\u20130.5% cross drop from tacky", reason: "scale guide (outlaw): freeing tracks transfer weight differently on offset cage" }, slick: { lever: "scale_guide_cond", action: "Slick: cross down 0.3\u20130.5% from tacky on tread RR \u2014 seat mm tweak before second cross change", reason: "scale guide (outlaw): heavy cross + slick often binds off throttle" }, greasy: { lever: "scale_guide_cond", action: "Greasy: cross mid-band \u2014 lower wing first; add angle only if loose entry after cross baseline", reason: "scale guide (outlaw): wing + cross stack on cage outlaw \u2014 start conservative" }, unknown: { lever: "scale_guide_cond", action: "When unsure: establish cross baseline on scales before hot laps \u2014 log left % with cross", reason: "scale guide (outlaw): offset scaling is repeatable foundation for the night" } }, flat_kart: { tacky: { lever: "scale_guide_cond", action: "Tacky: log cross/left/nose on scales before stagger or rear track \u2014 flat kart ~62\u201368% cross band", reason: "scale guide (flat kart): straight-rail scaling foundation on normal grip" }, drying: { lever: "scale_guide_cond", action: "Drying: cross down 0.3\u20130.5% after stagger down \u2014 seat tweak before second cross", reason: "scale guide (flat kart): freeing dirt oval wants less cross/stagger" }, slick: { lever: "scale_guide_cond", action: "Slick: cross down ~0.5\u20131% from tacky (~58\u201364%) \u2014 rear track/seat before second cross", reason: "scale guide (flat kart): heavy cross binds exit on slick straight-rail" }, greasy: { lever: "scale_guide_cond", action: "Greasy: conservative cross mid-band \u2014 lower left if RS overheats", reason: "scale guide (flat kart): wet stacks grip \u2014 cross/left pairing" }, unknown: { lever: "scale_guide_cond", action: "When unsure: return to last logged cross/left/nose baseline before new moves", reason: "scale guide (flat kart): your scale sheet beats generic bands" } }, micro_sprint_600: { tacky: { lever: "scale_guide_cond", action: "Tacky: square axle first, then ride height/block baseline \u2014 log left/rear % before bar or cross fine-tune", reason: "scale guide (600 micro): Hyper squaring before scaling chase" }, drying: { lever: "scale_guide_cond", action: "Drying: re-log left/rear after stagger change \u2014 cross fine-tune only after psi/stagger stable", reason: "scale guide (600 micro): freeing track \u2014 basics before cross collars" }, slick: { lever: "scale_guide_cond", action: "Slick: LR+RF weight or cross down slightly if car binds mid \u2014 after stagger and RR psi runs", reason: "scale guide (600 micro): Hyper tighten list \u2014 stagger/psi before scaling fine-tune" }, greasy: { lever: "scale_guide_cond", action: "Greasy: hold upper stagger band first \u2014 rescale only if seat or major ballast changed", reason: "scale guide (600 micro): wet often wants geometry before cross chase" }, unknown: { lever: "scale_guide_cond", action: "When unsure: Hyper order \u2014 square, ride height, left/rear log, then one scaling lever", reason: "scale guide (600 micro): consistent pad location beats guessing cross" } }, lightning_sprint: { tacky: { lever: "scale_guide_cond", action: "Tacky: Hyper squaring + ride height to torsion bar before panhard or cross chase \u2014 same driver rule", reason: 'scale guide (lightning): 13" midget-scale baseline \u2014 verify winged vs wingless sheet' }, drying: { lever: "scale_guide_cond", action: "Drying: ride height +1 RS / +2 LS is Hyper tighten ref \u2014 try before cross if entry push after stagger/psi", reason: "scale guide (lightning): freeing track geometry before cross stack" }, slick: { lever: "scale_guide_cond", action: "Slick: cross or LR+RF weight only after stagger down and RR psi stable \u2014 wing/Jacobs separate run", reason: "scale guide (lightning): Hyper slick sequence \u2014 basics before scaling fine-tune" }, greasy: { lever: "scale_guide_cond", action: "Greasy: conservative cross/wing \u2014 recheck LF tie-down and panhard before cross add", reason: "scale guide (lightning): heavy dirt reactive on light midget-scale cars" }, unknown: { lever: "scale_guide_cond", action: "When unsure: return to Hyper ride-height marks and logged left/rear before cross moves", reason: "scale guide (lightning): repeatable geometry beats nightly rescale guessing" } } }; var PRIORITY_BY_PROFILE = { quarter_midget: { lever: "scale_guide_priority", action: "Try scaling when: cross/left outside your sheet band, or exit binds after two psi runs \u2014 try psi/stagger first when: entry push on tacky with cross already in band", reason: "scale guide (QM): pressure is easier first lever; scaling fixes weight distribution issues" }, outlaw_kart: { lever: "scale_guide_priority", action: "Try cross/seat first when: balance off on tacky with logged stagger \u2014 try psi first when: RR tread growth or hot RR before cross stack", reason: "scale guide (outlaw): offset karts are cross-primary; psi supports RR management" }, flat_kart: { lever: "scale_guide_priority", action: "Try cross/seat/rear track first when: balance off with logged psi \u2014 try psi first when: hot RS off band on sealed tread (lap 8\u201310 log)", reason: "scale guide (flat kart): LO206/flat dirt oval \u2014 psi then scale family before shocks" }, micro_sprint_600: { lever: "scale_guide_priority", action: "Try scaling when: left/rear % drifted or ride height unset \u2014 try psi/stagger first when: Hyper bands look stable but balance unchanged", reason: "scale guide (600 micro): square + RH baseline before cross collars" }, lightning_sprint: { lever: "scale_guide_priority", action: "Try ride height/panhard when: entry push after psi+stagger logged \u2014 try psi/stagger first when: hot numbers off Hyper band", reason: "scale guide (lightning): not Maxim sprint \u2014 midget-scale Hyper dirt order" } }; function grassrootsScalingGuidance(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { scalingPriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; const priors = []; const hasClassScaling = skip.has("scaling") || skip.has("cross") || skip.has("weight"); const targets = TARGET_BANDS[profile2]; if (targets && !skip.has("scale_guide_targets") && !hasClassScaling) { priors.push(targets); } const effects = EFFECTS_BY_PROFILE[profile2]; if (effects && !skip.has("scale_guide_effects")) priors.push(effects); if (!skip.has("scale_guide_repeat")) priors.push(REPEAT_BASELINE); const cond = COND_BY_PROFILE2[profile2]?.[bucket] || COND_BY_PROFILE2[profile2]?.unknown; if (cond && !skip.has("scale_guide_cond") && !skip.has("surface_cross")) { priors.push(cond); } const priority = PRIORITY_BY_PROFILE[profile2]; if (priority && !skip.has("scale_guide_priority")) priors.push(priority); return { scalingPriors: priors.slice(0, maxPriors), surfaceBucket: bucket }; } // scripts/lib/experimental/grassrootsHandlingDiagnosis.mjs var GRASSROOTS_HANDLING_CAVEAT = "Directional handling fixes from public manufacturer and community patterns \u2014 log ONE change, re-run A-B-A on the same track state; your logged data overrides this chart."; var GRASSROOTS_HANDLING_SOURCE = "Grassroots dirt oval handling diagnosis guide (directional)"; var DISCIPLINE = { lever: "handle_discipline", action: "Before changing anything: write entry/mid/exit feel + tonight's one lever in notes \u2014 re-run A-B-A before booking a second move", reason: "handling guide: symptom logging is how grassroots teams learn; stacked fixes hide what worked" }; function detectGrassrootsHandlingSymptom(vc2 = {}) { const entry = String(vc2.trait_entry || "").toLowerCase(); const mid = String(vc2.trait_mid || "").toLowerCase(); const exit = String(vc2.trait_exit || "").toLowerCase(); const notes = String(vc2.trait_notes || "").toLowerCase(); const all = `${entry}|${mid}|${exit}|${notes}`; const entryTight = /push|tight|bind|flat|under|pushing|understeer/.test(entry); const midTight = /push|tight|bind|flat|under/.test(mid); const exitLoose = /loose|free|spin|fishtail|oversteer|snap|over rotate/.test(exit); const entryLoose = /loose|free|spin|fishtail/.test(entry); const midLoose = /loose|free|spin|fishtail/.test(mid); const exitTight = /tight|push|bind|flat/.test(exit); if ((entryTight || midTight) && exitLoose) { return { key: "tight_in_loose_off", label: "TIGHT IN \xB7 LOOSE OFF", phase: "classic" }; } if (/wheel hop|wheelhop|hopping|skipping|skips across|bouncing|porpois|chattering/.test(all)) { return { key: "wheel_hop", label: "WHEEL HOP", phase: "all" }; } if (/wash|up the track|up track|pushes up|runs up|drifts up|slides up/.test(all)) { return { key: "washes_up", label: "WASHES UP", phase: "entry" }; } if (/wont turn|won't turn|won’t turn|no turn|doesn't turn|doesnt turn|plow|pushing straight|won't rotate|wont rotate|doesn't rotate/.test(all)) { return { key: "wont_turn", label: "WON'T TURN", phase: "entry" }; } if (/flat center|dead center|lazy center|center flat|center push|no center rotation|won't rotate center|wont rotate center|flat in the middle/.test(all)) { return { key: "center_flat", label: "FLAT CENTER", phase: "mid" }; } if (entryTight) { return { key: "entry_push", label: "ENTRY PUSH", phase: "entry" }; } if (midTight) { return { key: "mid_push", label: "MID PUSH", phase: "mid" }; } if (exitLoose) { return { key: "exit_loose", label: "EXIT LOOSE", phase: "exit" }; } if (exitTight && !exitLoose) { return { key: "exit_tight", label: "EXIT PUSH", phase: "exit" }; } if (midLoose) { return { key: "mid_loose", label: "MID LOOSE", phase: "mid" }; } if (entryLoose && exitLoose) { return { key: "everywhere_loose", label: "LOOSE EVERYWHERE", phase: "all" }; } if (entryTight && exitTight) { return { key: "everywhere_tight", label: "TIGHT EVERYWHERE", phase: "all" }; } if (entryLoose) { return { key: "entry_loose", label: "ENTRY LOOSE", phase: "entry" }; } return null; } var FIXES = { quarter_midget: { entry_push: { action: "Entry push: try RF/RR psi down 1 psi (from ~10\u201311 public band) OR +1/16 in RF ride height per Stanley/RSR sheet \u2014 not both same run", reason: "handling (QM): Stanley/RSR norm \u2014 pressure or nose bite before cross on tacky; log hot psi after laps 8\u201310" }, mid_push: { action: "Mid push: cross down ~0.5% (X-method) OR stagger out 1/4 in effective \u2014 one lever, separate run from psi", reason: "handling (QM): center push often cross or effective stagger on offset QM \u2014 not camber first" }, exit_loose: { action: "Exit loose: stagger out 1/4 in OR RR psi up 1 \u2014 protect LF/LR from big drops (changes effective stagger)", reason: "handling (QM): exit free usually wants more rear bite or RS support \u2014 one step only" }, mid_loose: { action: "Mid loose: RF/RR psi up 1 OR cross up slightly \u2014 log which corner feels free before choosing", reason: "handling (QM): RS stiffer often loosens; confirm direction on your tire brand" }, tight_in_loose_off: { action: "Tight in / loose off: fix entry first \u2014 RF/RR psi down 1 OR small RH per sheet; do NOT loosen exit same run (stagger/cross on next run only)", reason: "handling (QM): classic dirt imbalance \u2014 never stack entry tighten + exit loosen one heat" }, wont_turn: { action: "Won't turn: RF/RR psi down 1 from baseline OR narrow track width to sheet \u2014 verify axle square before camber chase", reason: "handling (QM): QM push often psi or geometry baseline \u2014 squaring is free before shocks" }, washes_up: { action: "Washes up the track: ease RF/RR psi down 1 OR cross down 0.5% \u2014 entry push on dirt usually wants less RS spring or less cross", reason: "handling (QM): washing up is entry understeer \u2014 stagger rarely fixes before psi/cross" }, entry_loose: { action: "Entry loose: LF/LR psi up 1 OR cross down slightly \u2014 opposite of exit-loose fixes", reason: "handling (QM): entry free often too much rear bite or cross for conditions" }, everywhere_loose: { action: "Loose everywhere: return to last scaled baseline, then ONE tighten move \u2014 cross up 0.5% OR stagger in 1/4 in, not both", reason: "handling (QM): reset beats guessing when all three phases feel free" }, everywhere_tight: { action: "Tight everywhere: reset to baseline sheet, then ONE loosen move \u2014 RF/RR psi down 1 OR stagger out 1/4 in", reason: "handling (QM): stacked tight moves compound \u2014 undo to baseline first" }, exit_tight: { action: "Exit push: RR psi down 1 toward Stanley/RSR band OR cross down ~0.5% (X-method) \u2014 exit understeer wants rear unload or less cross, not more RS psi", reason: "handling (QM): exit push is rear overload or too much cross \u2014 public baseline before shock chase" }, center_flat: { action: "Flat center: cross down ~0.5% OR RF/RR psi down 1 \u2014 center won't rotate often cross high or RS too stiff for tonight", reason: "handling (QM): flat middle is mid understeer \u2014 verify cross ~52\u201354% band before camber" }, wheel_hop: { action: "Wheel hop: soften RF compression 1 click OR verify panhard height before stiffening anything \u2014 never add HS comp on rough dirt same run", reason: "handling (QM): hop is platform/skipping \u2014 psi and panhard before shock stacks on light QM cars" } }, outlaw_kart: { entry_push: { action: "Entry push: cross down 0.3\u20130.5% OR seat back slightly \u2014 log seat mm with cross; stagger before wing on tacky", reason: "handling (outlaw): offset cage \u2014 cross/seat primary; wing stacks with cross quickly" }, mid_push: { action: "Mid push: cross down OR rear stagger down 1/8 in \u2014 tread RR growth may need RR psi ease instead of more cross", reason: "handling (outlaw): mid push on offset kart \u2014 check RR temp before second cross change" }, exit_loose: { action: "Exit loose: rear stagger out 1/8 in OR cross up slightly \u2014 wing step is a separate run", reason: "handling (outlaw): exit free wants rear bite or cross \u2014 not wing + stagger same heat" }, mid_loose: { action: "Mid loose: cross up 0.3% OR wing angle up 0.5\xB0 \u2014 one lever only on 1/8 high-bank", reason: "handling (outlaw): outdoor offset tracks \u2014 cross and wing interact" }, tight_in_loose_off: { action: "Tight in / loose off: entry cross down OR seat mm first; exit fix on NEXT run \u2014 stagger out 1/8 in, not same heat", reason: "handling (outlaw): classic imbalance \u2014 Slack norm is cross/seat before wing on entry" }, wont_turn: { action: "Won't turn: verify toe/caster baseline, then cross down 0.3% \u2014 geometry before tire prep on offset outlaw", reason: "handling (outlaw): cage kart push often cross or front geometry, not RR compound first" }, washes_up: { action: "Washes up: cross down 0.3\u20130.5% OR ease RR psi if tread over-growing \u2014 entry load issue on offset chassis", reason: "handling (outlaw): washing up is entry push \u2014 wing rarely fixes before cross" }, entry_loose: { action: "Entry loose: cross up slightly OR wing down 0.5\xB0 \u2014 log whether loose is on throttle or off", reason: "handling (outlaw): entry free vs exit loose need different levers \u2014 note phase in log" }, everywhere_loose: { action: "Loose everywhere: reset cross + seat to baseline, then stagger in 1/8 in OR cross up 0.3% \u2014 one only", reason: "handling (outlaw): offset karts get reactive when cross + wing + stagger stack" }, everywhere_tight: { action: "Tight everywhere: reset to baseline, then stagger out 1/8 in OR cross down 0.3% \u2014 separate from wing", reason: "handling (outlaw): undo experiments before adding more tighten moves" }, exit_tight: { action: "Exit push: cross down 0.3% OR RR psi down 1 if tread over-growing \u2014 exit bind on offset kart often cross/seat, not wing first", reason: "handling (outlaw): Slack norm \u2014 exit push is load balance; log seat mm with cross" }, center_flat: { action: "Flat center: cross down 0.3\u20130.5% OR seat forward slightly \u2014 center dead on cage kart usually cross or seat, not stagger first", reason: "handling (outlaw): offset center push \u2014 verify cross ~59\u201366% public band before wing" }, wheel_hop: { action: "Wheel hop: soften RR compression OR move axle slot forward one hole \u2014 cross down 0.3% if hop started when track freed (QRC norm)", reason: "handling (outlaw): floater hop often axle slot + RR platform \u2014 wing and cross stack hide the fix" } }, flat_kart: { entry_push: { action: "Entry push: seat forward 1 hole OR cross down 0.3\u20130.5% \u2014 log seat hole with cross; not outlaw wing lever", reason: "handling (flat kart): nose via seat primary on straight-rail dirt oval" }, mid_push: { action: "Mid push: cross down OR rear stagger down 1/8 in \u2014 rear track in 2 mm if RS loaded", reason: "handling (flat kart): mid push \u2014 cross/rear track before shock" }, exit_loose: { action: "Exit loose: rear stagger out 1/8 in OR cross up 0.3% if LR unloaded \u2014 separate runs", reason: "handling (flat kart): exit free wants rear bite \u2014 moderate stagger vs outlaw" }, tight_in_loose_off: { action: "Tight in / loose off: entry seat/cross fix first; exit stagger/cross on NEXT run", reason: "handling (flat kart): phase rule \u2014 one end of track per heat" }, center_flat: { action: "Flat center: cross down 0.3\u20130.5% OR seat forward \u2014 verify cross ~62\u201368% tacky band", reason: "handling (flat kart): center dead often cross high for current grip" }, exit_tight: { action: "Exit push: cross down 0.3% OR rear track in 2 mm \u2014 not same run as seat forward", reason: "handling (flat kart): exit bind \u2014 scale family before shocks" }, mid_loose: { action: "Mid loose: cross up 0.3% if LR unloaded OR rear stagger in 1/8 in \u2014 log hot stagger before second cross move", reason: "handling (flat kart): mid free on straight-rail \u2014 cross + stagger before shock trim" }, entry_loose: { action: "Entry loose: seat back 1 hole OR cross down 0.3% \u2014 opposite of exit-loose fixes on flat kart", reason: "handling (flat kart): entry free often nose too low or cross too high for grip state" }, everywhere_loose: { action: "Loose everywhere: reset cross/seat to scaled baseline, then stagger in 1/8 in OR cross down 0.3% \u2014 one only", reason: "handling (flat kart): straight-rail karts get reactive when cross + stagger + rear track stack" }, everywhere_tight: { action: "Tight everywhere: reset to baseline sheet, then seat forward OR cross down 0.3% \u2014 separate from rear track", reason: "handling (flat kart): undo experiments before adding more tighten moves" }, wont_turn: { action: "Won't turn: seat forward 1 hole OR cross down 0.3% \u2014 verify scaled cross ~62\u201368% tacky band before camber chase", reason: "handling (flat kart): push on straight-rail dirt oval \u2014 seat/cross before rear track width" }, washes_up: { action: "Washes up: cross down 0.3\u20130.5% OR seat forward \u2014 entry push on flat kart usually wants less cross not more rear track", reason: "handling (flat kart): washing up is entry understeer \u2014 cross/seat before stagger on LO206 dirt oval" }, wheel_hop: { action: "Wheel hop: soften RF compression 1 click OR rear track in 2 mm \u2014 verify cross not high for freed grip before stiff stacks", reason: "handling (flat kart): hop is platform \u2014 cross family and RF comp before torsion/shock hero moves" } }, micro_sprint_600: { entry_push: { action: "Entry push: RF/RR psi down 1 OR +1 turn ride height all corners (Hyper drying ref) \u2014 wing back one hole if winged", reason: 'handling (600 micro): Hyper order \u2014 psi/RH before panhard or bar on 10" dirt' }, mid_push: { action: "Mid push: stagger down 1/4 in OR LR+RF weight slightly \u2014 RR psi down only if RR overheating", reason: "handling (600 micro): center push \u2014 stagger or cross before shock valving" }, exit_loose: { action: "Exit loose: stagger out 1/4 in OR RR psi up 1 \u2014 Hyper loosen: not wing + stagger same run", reason: "handling (600 micro): exit free wants rear bite \u2014 protect RR on slick" }, mid_loose: { action: "Mid loose: RF/RR psi up 1 OR soften RR bar one step \u2014 one Hyper loosen move only", reason: "handling (600 micro): RS stiffer loosens on Hyper 600 band" }, tight_in_loose_off: { action: "Tight in / loose off: entry RF/RR psi down 1 OR wing back (winged); exit stagger out on NEXT run \u2014 never both same heat", reason: "handling (600 micro): Hyper classic dirt \u2014 entry bite before exit loosen" }, wont_turn: { action: "Won't turn: square axle first, then RF/RR psi down 1 \u2014 Hyper squaring before bar or panhard chase", reason: "handling (600 micro): geometry baseline before handling chase" }, washes_up: { action: "Washes up: RF/RR psi down 1 OR stagger down 1/4 in \u2014 entry push on Hyper 600 dirt", reason: "handling (600 micro): washing up is understeer \u2014 shocks rarely first fix" }, entry_loose: { action: "Entry loose: wing forward one hole (winged) OR cross up slightly \u2014 log hot stagger before second move", reason: "handling (600 micro): entry free often wing or cross on small tracks" }, everywhere_loose: { action: "Loose everywhere: baseline reset, then stagger in 1/4 in OR RF/RR psi up 1 \u2014 Hyper one-lever rule", reason: "handling (600 micro): stacked loosen moves hide learning" }, everywhere_tight: { action: "Tight everywhere: reset to Hyper sheet, then stagger down 1/4 in OR RR psi down 1", reason: "handling (600 micro): basics before shock or wing stack" }, exit_tight: { action: "Exit push: RR psi down 1 OR LR+RR ride height down 1\u20132 turns (Hyper loosen) \u2014 exit bind wants rear platform, not wing forward same run", reason: 'handling (600 micro): Hyper 10" dirt \u2014 exit push after stagger logged stable' }, center_flat: { action: "Flat center: stagger down 1/4 in OR RF/RR psi down 1 \u2014 Hyper center push: basics before RR bar or panhard", reason: "handling (600 micro): flat middle on 600 \u2014 stagger/psi before bar stack" }, wheel_hop: { action: "Wheel hop: soften RF/RR compression 1 click \u2014 verify square axle and panhard before HS stiff; winged = wing back may calm platform", reason: "handling (600 micro): Hyper norm \u2014 hop is tire platform, not more spring rate on rough dirt" } }, micro_sprint_600_trailing: { entry_push: { action: "Entry push (trailing-arm): lower RR psi 1/4 OR RR in 1/2 in OR wing back 1\u20132 in \u2014 not cross and wing same run (PMP/GRE6)", reason: "handling (600 micro trailing): PMP entry tight list \u2014 psi/offset before bar turns on trailing-arm" }, mid_push: { action: "Mid push (trailing-arm): rear stagger down 1/4 in OR cross down 0.5% \u2014 log hot inch stagger before second bar turn", reason: "handling (600 micro trailing): center push \u2014 inch stagger primary on PMP/Stallard/D1 family" }, exit_loose: { action: "Exit loose (trailing-arm): raise front 4 coil turns OR stagger out 1/4 in \u2014 wing forward is separate run from stagger", reason: "handling (600 micro trailing): PMP exit bite \u2014 platform RH before Jacobs/ladder chase" }, mid_loose: { action: "Mid loose (trailing-arm): stagger in 1/4 in OR reduce LR shock rebound one step \u2014 one PMP loosen move only", reason: "handling (600 micro trailing): mid free \u2014 stagger before bar stack on trailing-arm" }, tight_in_loose_off: { action: "Tight in / loose off (trailing-arm): entry RR psi down OR wing back 1 in; exit stagger out on NEXT run \u2014 Factor 1 phase rule", reason: "handling (600 micro trailing): classic dirt \u2014 entry bite before exit loosen, never same heat" }, wont_turn: { action: "Won't turn (trailing-arm): RR in 1/2 in OR cross down 0.5% \u2014 square trailing arms before panhard chase", reason: "handling (600 micro trailing): geometry baseline before handling chase on trailing-arm micro" }, washes_up: { action: "Washes up (trailing-arm): RR psi down 1/4 OR rear stagger down 1/4 in \u2014 entry push on PMP dirt path", reason: "handling (600 micro trailing): washing up is understeer \u2014 stagger/psi before bar turns" }, entry_loose: { action: "Entry loose (trailing-arm): wing forward 1 in OR cross up slightly \u2014 log hot stagger before second move", reason: "handling (600 micro trailing): entry free often wing or cross on trailing-arm small tracks" }, everywhere_loose: { action: "Loose everywhere (trailing-arm): baseline reset, then stagger in 1/4 in OR cross down 0.5% \u2014 one lever per run", reason: "handling (600 micro trailing): stacked loosen moves hide learning on trailing-arm chassis" }, everywhere_tight: { action: "Tight everywhere (trailing-arm): reset to PMP/average sheet, then stagger down 1/4 in OR RR psi down 1/4", reason: "handling (600 micro trailing): basics before bar or wing stack" }, exit_tight: { action: "Exit push (trailing-arm): RR psi down 1/4 OR wing back 1 in \u2014 exit bind wants rear platform, not Jacobs same run", reason: "handling (600 micro trailing): exit push after stagger logged stable \u2014 PMP directional order" }, center_flat: { action: "Flat center (trailing-arm): stagger down 1/4 in OR cross down 0.5% \u2014 trailing-arm center push before RF bar chase", reason: "handling (600 micro trailing): flat middle \u2014 inch stagger/cross before bar stack" }, wheel_hop: { action: "Wheel hop (trailing-arm): reduce LR shock rebound OR soften RR bar 1/2 turn \u2014 no HS stiff on rough; verify block height first", reason: "handling (600 micro trailing): hop is platform \u2014 PMP/GRE6 bar/rebound before shock stacks" } }, lightning_sprint: { entry_push: { action: "Entry push: RF/RR psi down 1 OR +1 RS / +2 LS ride height (Hyper ref) \u2014 wing back if winged; Jacobs separate run", reason: 'handling (lightning): 13" Hyper dirt \u2014 not Maxim sprint shock logic' }, mid_push: { action: "Mid push: stagger down 1/4 in OR RR in toward 11 in offset \u2014 panhard check before bar change", reason: "handling (lightning): midget-scale Hyper mid push \u2014 stagger/RR before bars" }, exit_loose: { action: "Exit loose: stagger out 1/4 in OR lower ride heights 1\u20133 turns rear (Hyper loosen) \u2014 wing forward is separate run", reason: "handling (lightning): exit free \u2014 RR bite or RH tilt, one lever" }, mid_loose: { action: 'Mid loose: RF/RR psi up 1 OR wing forward \u2014 verify 13" sheet (winged vs wingless differ)', reason: "handling (lightning): RS psi or wing on Hyper Lightning band" }, tight_in_loose_off: { action: "Tight in / loose off: entry psi down 1 OR RH +1 RS; exit stagger out on NEXT run \u2014 wing/Jacobs not same heat", reason: "handling (lightning): classic dirt \u2014 Hyper ordered entry fix before exit" }, wont_turn: { action: "Won't turn: LF tie-down + panhard (~4 in) check, then RF/RR psi down 1 \u2014 square before ladder chase", reason: "handling (lightning): geometry and psi before full midget-style bar work" }, washes_up: { action: 'Washes up: RF/RR psi down 1 OR wing back \u2014 entry push on 13" Hyper dirt bullrings', reason: "handling (lightning): washing up is entry understeer \u2014 stagger second" }, entry_loose: { action: "Entry loose: wing forward OR Jacobs right hole (wingless slick ref) \u2014 log which config you run", reason: "handling (lightning): entry free on winged vs wingless uses different Hyper levers" }, everywhere_loose: { action: "Loose everywhere: reset to Hyper baseline, then stagger in 1/4 in OR RF/RR psi up 1", reason: "handling (lightning): one Hyper loosen move after baseline reset" }, everywhere_tight: { action: "Tight everywhere: reset sheet, then stagger down 1/4 in OR RR psi down 1 toward Hyper slick band", reason: "handling (lightning): undo before stacking tighten moves" }, exit_tight: { action: 'Exit push: RR psi down 1 OR wing back one hole (winged) \u2014 exit understeer on 13" Hyper; Jacobs/ladder is a separate run', reason: "handling (lightning): exit push \u2014 rear bite balance, not Maxim sprint bar logic" }, center_flat: { action: "Flat center: stagger down 1/4 in OR RF/RR psi down 1 \u2014 verify panhard ~4 in and LF tie-down before ladder chase", reason: "handling (lightning): midget-scale center push \u2014 geometry + psi before bars" }, wheel_hop: { action: 'Wheel hop: soften RF comp + verify LF tie-down \u2014 no HS stiff on rough 13" dirt; Jacobs check before ladder chase', reason: "handling (lightning): Hyper norm \u2014 hop is platform on midget-scale dirt, not more spring rate" } } }; var ALT_FIXES = { quarter_midget: { entry_push: { action: "Alt (next run): LF/LR psi up 1 if RR overheating OR +1/16 in RF ride height per sheet \u2014 only if RF/RR down did not help", reason: "handling alt (QM): public pattern when RS was already soft \u2014 directional baseline only" }, mid_push: { action: "Alt (next run): RF/RR psi down 1 if cross already at baseline OR narrow track width 1/16 in per Stanley sheet", reason: "handling alt (QM): center push alternate path \u2014 geometry before second cross move" }, exit_loose: { action: "Alt (next run): cross up ~0.5% OR LF/LR psi down 1 (changes effective stagger) \u2014 log hot stagger before cross", reason: "handling alt (QM): exit free alternate \u2014 weight transfer before second stagger step" }, mid_loose: { action: "Alt (next run): stagger in 1/4 in effective OR cross down 0.5% \u2014 opposite levers from mid loose psi path", reason: "handling alt (QM): confirm loose is mid not exit before tightening rear" }, exit_tight: { action: "Alt (next run): stagger out 1/4 in effective OR LF/LR psi up 1 \u2014 if RR down made exit worse, try rear bite", reason: "handling alt (QM): exit push sometimes needs stagger not less RR psi \u2014 log which direction helped" }, center_flat: { action: "Alt (next run): narrow track width to sheet OR ballast check if left % below ~55% band", reason: "handling alt (QM): flat center sometimes geometry/weight, not cross alone" }, tight_in_loose_off: { action: "Next run (exit phase): stagger out 1/4 in OR cross up ~0.5% \u2014 ONLY after entry fix confirmed in notes", reason: "handling alt (QM): classic dirt \u2014 entry and exit fixes on separate runs, never same heat" }, wont_turn: { action: "Alt (next run): cross down 0.5% if psi already eased OR recheck left % on scales (~55\u201358% band)", reason: "handling alt (QM): won't turn after psi \u2014 scaling check per public baseline" }, washes_up: { action: "Alt (next run): LF/LR psi up 1 if cross already down OR +1/16 in LR ride height \u2014 entry wash often nose light", reason: "handling alt (QM): washes up alternate \u2014 RS support without stacking cross drops" }, entry_loose: { action: "Alt (next run): stagger in 1/4 in OR RF/RR psi up 1 \u2014 tighten entry without cross if cross already low", reason: "handling alt (QM): entry free \u2014 opposite path from cross-down first move" }, everywhere_loose: { action: "Alt (next run): RF/RR psi up 1 if cross/stagger already moved \u2014 reset baseline if three moves failed", reason: "handling alt (QM): loose everywhere \u2014 one tighten lever after baseline reset" }, everywhere_tight: { action: "Alt (next run): cross down 0.5% if psi/stagger already eased \u2014 stop if two loosen moves failed", reason: "handling alt (QM): tight everywhere \u2014 scaling lever after psi path" }, wheel_hop: { action: "Alt (next run): LF rebound down 1 click if RF already soft OR narrow track width 1/16 in \u2014 stop if hop persists after platform fix", reason: "handling alt (QM): hop alternate \u2014 geometry/weight before second shock move" } }, outlaw_kart: { entry_push: { action: "Alt (next run): rear stagger out 1/8 in OR wing down 0.5\xB0 \u2014 only if cross/seat path did not help", reason: "handling alt (outlaw): entry push \u2014 stagger/wing after cross baseline" }, mid_push: { action: "Alt (next run): seat forward slightly OR RF psi down 1 \u2014 log seat mm with every cross change", reason: "handling alt (outlaw): mid push \u2014 weight distribution on offset kart" }, exit_loose: { action: "Alt (next run): wing up 0.5\xB0 OR cross up 0.3% \u2014 separate from stagger same heat", reason: "handling alt (outlaw): exit free \u2014 aero/cross after stagger logged" }, mid_loose: { action: "Alt (next run): stagger in 1/8 in OR wing down 0.5\xB0 \u2014 tighten mid without second cross bump", reason: "handling alt (outlaw): mid loose alternate \u2014 offset kart balance" }, exit_tight: { action: "Alt (next run): seat back slightly OR stagger out 1/8 in \u2014 if cross down tightened exit more", reason: "handling alt (outlaw): exit push \u2014 seat mm often pairs with cross on cage karts" }, center_flat: { action: "Alt (next run): RR psi down 1 if tread over-growing OR wing down 0.5\xB0 on high-bank", reason: "handling alt (outlaw): flat center \u2014 tire prep before second cross cut" }, tight_in_loose_off: { action: "Next run (exit phase): stagger out 1/8 in OR wing up 0.5\xB0 \u2014 only after entry cross/seat logged better", reason: "handling alt (outlaw): separate entry and exit runs \u2014 Slack norm" }, wont_turn: { action: "Alt (next run): seat forward 5\u201310 mm OR stagger down 1/8 in \u2014 geometry before second cross cut", reason: "handling alt (outlaw): won't turn \u2014 offset weight path" }, washes_up: { action: "Alt (next run): wing down 0.5\xB0 OR seat forward slightly \u2014 entry wash on offset outdoor tracks", reason: "handling alt (outlaw): washes up \u2014 aero/weight before tire prep stack" }, entry_loose: { action: "Alt (next run): stagger in 1/8 in OR seat back slightly \u2014 tighten entry without wing if cross already up", reason: "handling alt (outlaw): entry free alternate path" }, everywhere_loose: { action: "Alt (next run): wing down 0.5\xB0 if cross/stagger already tightened \u2014 baseline reset if still lost", reason: "handling alt (outlaw): loose everywhere \u2014 one more tighten lever max" }, everywhere_tight: { action: "Alt (next run): cross down 0.3% if stagger already out \u2014 stop after two loosen moves", reason: "handling alt (outlaw): tight everywhere \u2014 cross after stagger path" }, wheel_hop: { action: "Alt (next run): wing down 0.5\xB0 if axle slot moved OR RF psi down 1/4 \u2014 log slot mm with cross every run", reason: "handling alt (outlaw): hop alternate \u2014 aero unload before second platform change" } }, micro_sprint_600: { entry_push: { action: "Alt (next run): stagger down 1/4 in OR LR bar soften one step \u2014 Hyper basics before panhard", reason: "handling alt (600 micro): entry push \u2014 stagger/RH after psi path" }, mid_push: { action: "Alt (next run): cross/left % check on scales OR RF/RR psi down 1 \u2014 Hyper center push alternate", reason: "handling alt (600 micro): mid push \u2014 scaling before second stagger move" }, exit_loose: { action: "Alt (next run): wing forward one hole (winged) OR LR+RR RH down 1 turn \u2014 Hyper exit bite alternate", reason: "handling alt (600 micro): exit loose \u2014 platform after stagger" }, mid_loose: { action: 'Alt (next run): stagger in 1/4 in OR wing back one hole \u2014 tighten mid on 10" dirt', reason: "handling alt (600 micro): mid loose \u2014 Hyper one-lever alternate" }, exit_tight: { action: "Alt (next run): stagger out 1/4 in OR wing forward one hole \u2014 if RR down hurt exit drive", reason: "handling alt (600 micro): exit push \u2014 rear bite alternate" }, center_flat: { action: "Alt (next run): left/rear % on scales OR wing back one hole \u2014 Hyper flat center weight check", reason: "handling alt (600 micro): flat center \u2014 scaling before bar change" }, tight_in_loose_off: { action: "Next run (exit phase): stagger out 1/4 in OR RR psi up 1 \u2014 entry fix must be in notes first", reason: "handling alt (600 micro): Hyper classic \u2014 separate runs" }, wont_turn: { action: "Alt (next run): panhard height check OR cross/left % on scales \u2014 Hyper geometry before second psi drop", reason: "handling alt (600 micro): won't turn \u2014 square + scale path" }, washes_up: { action: "Alt (next run): wing back one hole OR +1 turn all-corner RH \u2014 Hyper entry wash alternate", reason: "handling alt (600 micro): washes up \u2014 platform before psi stack" }, entry_loose: { action: "Alt (next run): RF/RR psi up 1 OR stagger in 1/4 in \u2014 tighten entry Hyper order", reason: "handling alt (600 micro): entry free alternate" }, everywhere_loose: { action: "Alt (next run): cross up slightly on scales OR wing down \u2014 reset if three moves failed", reason: "handling alt (600 micro): loose everywhere \u2014 one tighten after reset" }, everywhere_tight: { action: "Alt (next run): RF/RR psi down 1 if stagger already in \u2014 stop after baseline reset + one move", reason: "handling alt (600 micro): tight everywhere alternate" }, wheel_hop: { action: "Alt (next run): wing back one hole (winged) OR soften RR bar 1/2 turn \u2014 verify square before second shock click", reason: "handling alt (600 micro): hop alternate \u2014 aero/platform before HS stiff" } }, lightning_sprint: { entry_push: { action: 'Alt (next run): stagger down 1/4 in OR Jacobs left one hole (wingless) \u2014 Hyper 13" after psi/RH', reason: "handling alt (lightning): entry push \u2014 not Maxim sprint ladder first" }, mid_push: { action: "Alt (next run): RF/RR psi down 1 OR left % check on scales \u2014 Hyper mid push alternate", reason: "handling alt (lightning): center push \u2014 psi before second stagger" }, exit_loose: { action: "Alt (next run): RR psi up 1 OR wing forward one hole \u2014 Hyper exit alternate after stagger", reason: "handling alt (lightning): exit loose \u2014 one lever path" }, mid_loose: { action: "Alt (next run): stagger in 1/4 in OR Jacobs right (wingless slick ref) \u2014 log winged vs wingless", reason: "handling alt (lightning): mid loose \u2014 config-specific alternate" }, exit_tight: { action: "Alt (next run): stagger out 1/4 in OR LR+RR RH down 1\u20133 turns \u2014 if wing back over-tightened exit", reason: "handling alt (lightning): exit push \u2014 rear platform alternate" }, center_flat: { action: "Alt (next run): RF/RR psi down 1 OR Jacobs center hole check (wingless) \u2014 panhard before ladder", reason: "handling alt (lightning): flat center \u2014 Hyper geometry path" }, tight_in_loose_off: { action: "Next run (exit phase): stagger out 1/4 in OR wing forward one hole \u2014 entry fix logged first", reason: "handling alt (lightning): Hyper ordered entry then exit" }, wont_turn: { action: "Alt (next run): stagger down 1/4 in OR scale left % \u2014 LF tie-down verified before Jacobs chase", reason: "handling alt (lightning): won't turn alternate \u2014 midget-scale only" }, washes_up: { action: 'Alt (next run): stagger down 1/4 in OR +1 RS ride height \u2014 13" bullring entry wash', reason: "handling alt (lightning): washes up \u2014 RH/stagger after psi" }, entry_loose: { action: "Alt (next run): RF/RR psi up 1 OR stagger in 1/4 in \u2014 tighten entry on Hyper sheet", reason: "handling alt (lightning): entry free alternate" }, everywhere_loose: { action: "Alt (next run): wing down or Jacobs tighten (wingless) if stagger already in \u2014 reset if lost", reason: "handling alt (lightning): loose everywhere \u2014 one more tighten max" }, everywhere_tight: { action: "Alt (next run): RF/RR psi down 1 if stagger already out \u2014 baseline reset beats stacking", reason: "handling alt (lightning): tight everywhere alternate" }, wheel_hop: { action: "Alt (next run): Jacobs center hole check (wingless) OR RR soft comp \u2014 LF tie-down verified before ladder", reason: "handling alt (lightning): hop alternate \u2014 geometry before second shock move" } }, flat_kart: { entry_push: { action: "Alt (next run): rear track in 2 mm if seat/cross path did not help OR RF psi down 1/4 \u2014 log seat hole with cross", reason: "handling alt (flat kart): entry push \u2014 rear track after seat/cross baseline" }, mid_push: { action: "Alt (next run): seat forward 1 hole OR RF psi down 1/4 \u2014 log scaled cross before second stagger move", reason: "handling alt (flat kart): mid push \u2014 weight distribution on straight-rail" }, exit_loose: { action: "Alt (next run): cross up 0.3% OR rear track out 2 mm \u2014 separate from stagger same heat", reason: "handling alt (flat kart): exit free \u2014 cross/rear track after stagger logged" }, mid_loose: { action: "Alt (next run): rear track in 2 mm OR RF psi up 1/4 \u2014 tighten mid without second cross bump", reason: "handling alt (flat kart): mid loose alternate \u2014 straight-rail balance" }, exit_tight: { action: "Alt (next run): stagger out 1/8 in OR seat back 1 hole \u2014 if cross down tightened exit more", reason: "handling alt (flat kart): exit push \u2014 seat often pairs with cross on flat karts" }, center_flat: { action: "Alt (next run): rear track in 2 mm if cross already down OR recheck left % ~56\u201362% band", reason: "handling alt (flat kart): flat center sometimes rear track/weight, not cross alone" }, tight_in_loose_off: { action: "Next run (exit phase): stagger out 1/8 in OR cross up 0.3% \u2014 only after entry seat/cross logged better", reason: "handling alt (flat kart): separate entry and exit runs \u2014 phase rule" }, wont_turn: { action: "Alt (next run): rear track in 2 mm OR cross down 0.3% \u2014 geometry before second seat move", reason: "handling alt (flat kart): won't turn \u2014 straight-rail weight path" }, washes_up: { action: "Alt (next run): RF psi down 1/4 OR seat forward \u2014 entry wash on flat dirt oval", reason: "handling alt (flat kart): washes up \u2014 nose weight before tire prep stack" }, entry_loose: { action: "Alt (next run): stagger in 1/8 in OR cross down 0.3% \u2014 tighten entry without seat if already back", reason: "handling alt (flat kart): entry free alternate path" }, everywhere_loose: { action: "Alt (next run): seat forward if cross/stagger already tightened \u2014 baseline reset if still lost", reason: "handling alt (flat kart): loose everywhere \u2014 one more tighten lever max" }, everywhere_tight: { action: "Alt (next run): cross down 0.3% if stagger already out \u2014 stop after two loosen moves", reason: "handling alt (flat kart): tight everywhere \u2014 cross after stagger path" }, wheel_hop: { action: "Alt (next run): cross down 0.3% if RF already soft OR seat forward \u2014 log cross with rear track every run", reason: "handling alt (flat kart): hop alternate \u2014 cross family before second shock click" } }, micro_sprint_600_trailing: { entry_push: { action: "Alt (next run): cross down 0.5% if RR psi/offset path did not help OR Jacobs toward hole 1 \u2014 PMP after psi path", reason: "handling alt (600 micro trailing): entry push \u2014 cross after psi/offset baseline" }, mid_push: { action: "Alt (next run): RF/RR psi down 1/4 OR cross/left % check on scales \u2014 trailing-arm center push alternate", reason: "handling alt (600 micro trailing): mid push \u2014 scaling before second stagger move" }, exit_loose: { action: "Alt (next run): wing forward 1 in (winged) OR add LR weight \u2014 PMP exit bite alternate", reason: "handling alt (600 micro trailing): exit loose \u2014 platform after stagger" }, mid_loose: { action: "Alt (next run): cross up 0.5% OR wing back 1 in \u2014 tighten mid on trailing-arm dirt", reason: "handling alt (600 micro trailing): mid loose \u2014 one-lever alternate" }, exit_tight: { action: "Alt (next run): stagger out 1/4 in OR raise front RH 2 coil turns \u2014 if RR down hurt exit drive", reason: "handling alt (600 micro trailing): exit push \u2014 rear bite alternate" }, center_flat: { action: "Alt (next run): left/rear % on scales OR wing back 1 in \u2014 trailing-arm flat center weight check", reason: "handling alt (600 micro trailing): flat center \u2014 scaling before bar change" }, tight_in_loose_off: { action: "Next run (exit phase): stagger out 1/4 in OR RR psi up 1/4 \u2014 entry fix must be in notes first", reason: "handling alt (600 micro trailing): PMP phase rule \u2014 separate runs" }, wont_turn: { action: "Alt (next run): panhard in 1/2 in OR cross down 0.5% \u2014 trailing-arm geometry before second psi drop", reason: "handling alt (600 micro trailing): won't turn \u2014 square + scale path" }, washes_up: { action: "Alt (next run): wing back 1 in OR +1/2 torsion turn all corners \u2014 PMP entry wash alternate", reason: "handling alt (600 micro trailing): washes up \u2014 platform before psi stack" }, entry_loose: { action: "Alt (next run): RF/RR psi up 1/4 OR stagger in 1/4 in \u2014 tighten entry PMP order", reason: "handling alt (600 micro trailing): entry free alternate" }, everywhere_loose: { action: "Alt (next run): cross down 0.5% on scales OR wing down \u2014 reset if three moves failed", reason: "handling alt (600 micro trailing): loose everywhere \u2014 one tighten after reset" }, everywhere_tight: { action: "Alt (next run): RR psi down 1/4 if stagger already in \u2014 stop after baseline reset + one move", reason: "handling alt (600 micro trailing): tight everywhere alternate" }, wheel_hop: { action: "Alt (next run): wing back 1 in (winged) OR verify block height \u2014 square before second bar turn", reason: "handling alt (600 micro trailing): hop alternate \u2014 platform before HS stiff" } } }; var BUCKET_HINT = { drying: " \xB7 drying track: less RS psi/stagger before geometry", slick: " \xB7 slick: protect RR \u2014 stagger/psi before cross stack", greasy: " \xB7 greasy/wet: conservative cross/seat \u2014 one lever", tacky: "", unknown: "" }; var TRACK_FIRST_MOVE = { quarter_midget: { slick: { entry_push: "Slick first: RF/RR down 1 psi \u2014 ", mid_push: "Slick first: cross down ~0.5% before stagger \u2014 ", exit_loose: "Slick first: stagger out 1/4 in before cross \u2014 ", wheel_hop: "Slick hop: soft RF comp first \u2014 " }, drying: { entry_push: "Drying first: RF/RR down 1 before cross \u2014 ", tight_in_loose_off: "Drying: fix entry psi/RH this run; exit next run only \u2014 " }, greasy: { entry_loose: "Wet first: LF/LR psi up 1 before cross \u2014 " } }, outlaw_kart: { slick: { entry_push: "Slick first: cross down 0.3% OR axle forward slot \u2014 ", exit_loose: "Slick first: stagger out 1/8 in \u2014 wing separate run \u2014 ", wheel_hop: "Slick hop: soften RR comp; check axle slot \u2014 " }, drying: { tight_in_loose_off: "Drying entry: cross down + wing back 0.5\xB0 \u2014 ", exit_tight: "Drying exit bind: cross down 0.3% \u2014 " }, greasy: { entry_push: "Wet first: cross/seat mid-band before max stagger \u2014 " } }, flat_kart: { slick: { entry_push: "Slick first: cross down 0.5% before rear track \u2014 ", mid_push: "Slick first: stagger down 1/8 in before cross add \u2014 ", exit_loose: "Slick first: moderate stagger; cross up only if LR unloaded \u2014 " }, drying: { center_flat: "Drying center: cross down 0.3% OR stagger down 1/8 in \u2014 " }, greasy: { entry_loose: "Wet first: conservative cross + seat forward if push \u2014 " } }, micro_sprint_600: { slick: { entry_push: "Slick first: RF/RR down 1 OR wing back \u2014 ", exit_loose: "Slick first: stagger out 1/4 in \u2014 protect RR \u2014 ", wheel_hop: "Slick hop: soften RF/RR comp \u2014 no HS stiff \u2014 " }, drying: { tight_in_loose_off: "Drying: entry psi/RH this run; exit stagger next run \u2014 " } }, micro_sprint_600_trailing: { slick: { entry_push: "Slick first: rear stagger down 1/4 in + RR psi down \u2014 ", exit_loose: "Slick first: less stagger (~4\u20136 in) + wing back 1 in \u2014 " }, drying: { tight_in_loose_off: "Drying: stagger down before wing/cross add (PMP) \u2014 " }, greasy: { entry_loose: "Wet first: hold cross mid-band; upper rear stagger if loose \u2014 " } } }; function resolveFixProfile(profile2, enrichedVc) { if (profile2 === "micro_sprint_600" && isMicroNonHyperDeepBuilder(enrichedVc.chassis_mfr_key)) { return "micro_sprint_600_trailing"; } return profile2; } function applyTrackFirstMove(fix, fixProfile, bucket, symptomKey) { const prefix = TRACK_FIRST_MOVE[fixProfile]?.[bucket]?.[symptomKey]; if (!prefix) return fix; return { ...fix, action: `${prefix}${fix.action}` }; } function grassrootsHandlingDiagnosis(trackState, profile2, vc2 = {}, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { handlingPriors: [], diagnosedSymptom: null }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const enriched = enrichGrassrootsVehicleContext(vc2, profile2); const symptom = detectGrassrootsHandlingSymptom(enriched); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; if (!symptom) { return { handlingPriors: [], diagnosedSymptom: null }; } const fixProfile = resolveFixProfile(profile2, enriched); const baseFix = FIXES[fixProfile]?.[symptom.key]; const fix = baseFix ? applyTrackFirstMove(baseFix, fixProfile, bucket, symptom.key) : null; if (!fix || skip.has(`handle_fix_${symptom.key}`)) { return { handlingPriors: [], diagnosedSymptom: symptom }; } const hint = BUCKET_HINT[bucket] || BUCKET_HINT.unknown; const feelNote = ` \xB7 feel logged: entry=${enriched.trait_entry || "\u2014"} mid=${enriched.trait_mid || "\u2014"} exit=${enriched.trait_exit || "\u2014"}`; const chassisNote = fixProfile === "micro_sprint_600_trailing" ? ` \xB7 chassis: ${enriched.chassis_manufacturer || "trailing-arm micro"}` : ""; const priors = [{ lever: `handle_fix_${symptom.key}`, action: `${symptom.label}: ${fix.action}${hint}`, reason: `${fix.reason}${feelNote}${chassisNote}` }]; const alt = ALT_FIXES[fixProfile]?.[symptom.key]; const altLever = `handle_alt_${symptom.key}`; if (alt && !skip.has(altLever)) { priors.push({ lever: altLever, action: alt.action, reason: `${alt.reason}${feelNote}` }); } if (!skip.has("handle_discipline") && !skip.has("your_symptom")) { priors.push(DISCIPLINE); } return { handlingPriors: priors.slice(0, maxPriors), diagnosedSymptom: symptom, surfaceBucket: bucket }; } // scripts/lib/experimental/grassrootsShockGuidance.mjs var GRASSROOTS_SHOCK_CAVEAT = "Basic shock guidance from public manufacturer patterns \u2014 fine-tune only after psi, stagger, and ride height are logged stable; one click per corner per run; not full midget/Maxim sprint valving logic."; var GRASSROOTS_SHOCK_SOURCE = "Grassroots dirt oval shock guide (basic / directional)"; var PHILOSOPHY8 = { lever: "shock_guide_philosophy", action: "Shock basics: compression = how fast weight loads that corner (entry/mid) \xB7 rebound = how fast weight unloads (mid/exit) \xB7 gas pressure is fine-tune only \u2014 not a spring substitute", reason: "shock guide: grassroots dirt \u2014 shocks trim platform after tire and geometry baseline" }; var WHEN_FIRST = { lever: "shock_guide_when", action: "Adjust shocks ONLY after psi, stagger, and ride height are logged stable for the night \u2014 if two basic runs failed, reset baseline before shock chasing", reason: "shock guide: #1 grassroots mistake is random shock changes before tire/geometry" }; var START_BY_PROFILE = { quarter_midget: { lever: "shock_guide_start", action: "QM starting philosophy: many quarter midgets run manufacturer baseline valving \u2014 if adjustable, start full stiff minus ~2\u20134 clicks per corner and log positions before hot laps", reason: "shock guide (QM): Stanley/Bullrider sheets vary \u2014 stock or near-stock is common on spec-tire QM" }, outlaw_kart: { lever: "shock_guide_start", action: "Outlaw starting philosophy: log compression + rebound positions on all four corners \u2014 start near builder/Slack baseline; one click (comp OR reb) per corner per run", reason: "shock guide (outlaw): offset cage kart \u2014 cross/seat/stagger before coilover chasing" }, flat_kart: { lever: "shock_guide_start", action: "Flat kart starting philosophy: log shock positions at baseline \u2014 cross/seat/rear track/psi stable before one click per corner; straight-rail not winged outlaw valving", reason: "shock guide (flat kart): shock fine-tune after scale family on dirt oval flat kart" }, micro_sprint_600: { lever: "shock_guide_start", action: "Hyper 600 starting ref: full stiff minus ~2\u20134 clicks per corner (verify Hyper manual for torsion vs coil) \u2014 log valving before changing bars or wing", reason: 'shock guide (600 micro): Hyper public band \u2014 conservative clickers on 10" dirt' }, lightning_sprint: { lever: "shock_guide_start", action: "Hyper Lightning winged ref: LR full stiff \u22121-1/2 / RR \u22121 / RF \u22121-1/2 / LF \u22121 turn + LF tie-down per sheet (wingless PDF differs \u2014 verify config)", reason: 'shock guide (lightning): 13" midget-scale Hyper \u2014 not Maxim sprint shock charts' } }; var BUCKET_NOTES = { tacky: { lever: "shock_guide_cond", action: "Tacky: hold baseline shock positions unless psi/stagger are stable \u2014 fine-tune one corner one click after hot lap notes", reason: "shock guide: normal grip rarely needs big valving moves on first night" }, drying: { lever: "shock_guide_cond", action: "Drying: stagger/psi usually lead \u2014 if platform still hops, try LF reb up 1 click OR RF comp soft 1 before rear shock stack", reason: "shock guide: freeing track \u2014 basics before valving on all grassroots classes" }, slick: { lever: "shock_guide_cond", action: "Slick: protect RR bite first (psi/stagger) \u2014 shock fine-tune: RR soft comp or RF stiff comp one click (Hyper dirt tighten ref on micro/lightning)", reason: "shock guide: slick push rarely fixed by stacking four-corner shock changes" }, greasy: { lever: "shock_guide_cond", action: "Greasy: soften front comp slightly for wet entry before rear shock chase \u2014 still one corner per run", reason: "shock guide: stiff nose pushes on heavy tracks \u2014 conservative front comp move" }, unknown: { lever: "shock_guide_cond", action: "When unsure: leave shocks at logged baseline \u2014 fix psi/stagger/RH first, then one shock click on the corner that matches your symptom", reason: "shock guide: directional only \u2014 validate with A-B-A on your car" } }; var SYMPTOM_SHOCK = { quarter_midget: { entry_push: { action: "After psi/RH run: try RF soft comp 1 click OR LF reb up 1 \u2014 not both same heat", reason: "shock fine-tune (QM): entry push shock trim \u2014 only if basics unchanged" }, exit_loose: { action: "After stagger run: try RR reb up 1 click OR LR soft comp 1 \u2014 log which corner feels free", reason: "shock fine-tune (QM): exit loose \u2014 rear platform trim one click" }, tight_in_loose_off: { action: "Shocks AFTER entry fix: do not loosen exit same run \u2014 next run only: RR reb up 1 if exit still free", reason: "shock fine-tune (QM): classic imbalance \u2014 never stack entry + exit shock same heat" }, mid_push: { action: "Mid push fine-tune: RF soft comp 1 OR cross already addressed \u2014 one click only", reason: "shock fine-tune (QM): center push rarely needs four-corner shock overhaul" }, mid_loose: { action: "Mid loose fine-tune: RR stiff comp 1 click \u2014 confirm psi/stagger not already loose", reason: "shock fine-tune (QM): verify basics before rear comp stiffen" }, exit_tight: { action: "After RR psi/cross run: try LR reb up 1 OR RF soft comp 1 \u2014 exit push shock trim only if basics stable", reason: "shock fine-tune (QM): exit bind \u2014 front platform trim one click" }, center_flat: { action: "After cross/psi run: RF soft comp 1 click OR LF reb up 1 \u2014 center flat rarely needs four-corner valving", reason: "shock fine-tune (QM): flat center \u2014 one corner one click" } }, outlaw_kart: { entry_push: { action: "After cross/seat run: RF soft comp 1 OR LF reb up 1 \u2014 coilover one direction per run", reason: "shock fine-tune (outlaw): entry push \u2014 shocks after cross baseline" }, exit_loose: { action: "After stagger run: RR reb up 1 OR LR soft comp 1 \u2014 wing is separate run", reason: "shock fine-tune (outlaw): exit free \u2014 rear shock trim only" }, tight_in_loose_off: { action: "Shocks after entry cross/seat fix \u2014 next run: RR reb up 1 if exit still loose, not same heat", reason: "shock fine-tune (outlaw): never stack entry tighten + exit loosen shocks" }, exit_tight: { action: "After cross/RR psi: RF soft comp 1 OR LR reb up 1 \u2014 exit push trim one click only", reason: "shock fine-tune (outlaw): exit bind after geometry baseline" }, center_flat: { action: "After cross run: RF soft comp 1 \u2014 center flat shock trim after cross/seat logged", reason: "shock fine-tune (outlaw): flat center \u2014 conservative front comp" } }, micro_sprint_600: { entry_push: { action: "Hyper fine-tune after psi/RH: RF soft comp 1 + stiff RF comp on slick ref \u2014 one OR the other per run", reason: "shock fine-tune (600 micro): Hyper winged tighten uses RF comp \u2014 basics first" }, exit_loose: { action: "After stagger: RR soft comp 1 OR stiffen RR reb 1 (Hyper loosen: stiff RR comp) \u2014 one click", reason: "shock fine-tune (600 micro): Hyper dirt lists \u2014 rear platform one lever" }, tight_in_loose_off: { action: "Entry: RF comp per Hyper tighten \u2014 exit: next run RR reb, not same heat as wing/stagger", reason: "shock fine-tune (600 micro): ordered Hyper discipline" }, exit_tight: { action: "After RR psi/RH: RF soft comp 1 OR LR reb up 1 \u2014 Hyper exit push one click", reason: "shock fine-tune (600 micro): exit bind after basics" }, center_flat: { action: "After stagger/psi: RF soft comp 1 \u2014 Hyper center flat trim one corner", reason: "shock fine-tune (600 micro): flat center \u2014 not bar stack first" } }, lightning_sprint: { entry_push: { action: "Hyper fine-tune after psi/RH: RR soft comp + RF stiff comp (winged slick ref) \u2014 one corner one click only", reason: 'shock fine-tune (lightning): not Maxim sprint \u2014 Hyper 13" band' }, exit_loose: { action: "Wingless: reduce LF tie-down to tighten off (Hyper note) \xB7 Winged: RR reb up 1 after stagger logged", reason: "shock fine-tune (lightning): winged vs wingless differ \u2014 log config" }, tight_in_loose_off: { action: "Entry shock/RH first \u2014 exit fine-tune on NEXT run: wingless LF tie-down or winged RR reb 1", reason: "shock fine-tune (lightning): classic dirt \u2014 separate runs" }, exit_tight: { action: "After psi/wing: RF soft comp 1 OR LR reb up 1 \u2014 Hyper exit push, not Maxim ladder", reason: "shock fine-tune (lightning): exit bind one click" }, center_flat: { action: 'After stagger/psi: RF soft comp 1 \u2014 13" Hyper center trim before Jacobs', reason: "shock fine-tune (lightning): flat center conservative" } } }; function grassrootsShockGuidance(trackState, profile2, vc2 = {}, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { shockPriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; const enriched = enrichGrassrootsVehicleContext(vc2, profile2); const symptom = detectGrassrootsHandlingSymptom(enriched); const priors = []; if (!skip.has("shock_guide_philosophy")) priors.push(PHILOSOPHY8); if (!skip.has("shock_guide_when")) { priors.push(WHEN_FIRST); } const hasClassShock = skip.has("shock") || skip.has("shock_adj"); const start = START_BY_PROFILE[profile2]; if (start && !skip.has("shock_guide_start") && !hasClassShock) { priors.push(start); } if (symptom) { const fine = SYMPTOM_SHOCK[profile2]?.[symptom.key]; if (fine && !skip.has(`shock_guide_${symptom.key}`)) { priors.push({ lever: `shock_guide_${symptom.key}`, action: `Shock fine-tune (${symptom.label}): ${fine.action}`, reason: fine.reason }); } } const cond = BUCKET_NOTES[bucket] || BUCKET_NOTES.unknown; if (cond && !skip.has("shock_guide_cond")) { priors.push(cond); } const seen = /* @__PURE__ */ new Set(); const shockPriors = priors.filter((p) => { if (seen.has(p.lever)) return false; seen.add(p.lever); return !skip.has(p.lever); }).slice(0, maxPriors); return { shockPriors, surfaceBucket: bucket }; } // scripts/lib/experimental/grassrootsPracticeStrategy.mjs var GRASSROOTS_PRACTICE_CAVEAT = "Practice-night game plan from grassroots best practices \u2014 adapt to your track schedule; logged A-B-A runs beat any checklist."; var GRASSROOTS_PRACTICE_SOURCE = "Grassroots dirt oval practice session guide"; var STRUCTURE = { lever: "practice_structure", action: "Practice plan: (1) baseline run \u2014 3\u20135 clean laps on your sheet, no changes (2) log feel + hot psi/stagger (3) ONE planned test run (4) confirm or undo before the next test", reason: "practice guide: structure beats random laps \u2014 you learn what the track and car actually want" }; var LOG_CHECKLIST = { lever: "practice_log", action: "Log each practice run: track state \xB7 lap time or rank \xB7 entry/mid/exit feel \xB7 exact change made \xB7 hot psi/stagger if you adjusted tires", reason: "practice guide: Crew Chief and your future self need the same notes \u2014 handwriting one line per run is enough" }; var VALIDATE_BASELINE = { lever: "practice_validate", action: "Use practice to validate public baseline bands \u2014 run your manufacturer sheet, compare hot numbers to class guide, then A-B-A one psi or 1/4 in stagger step", reason: "practice guide: practice is for learning the baseline, not inventing a new setup every lap" }; var TEST_VS_SAVE = { lever: "practice_test_vs_save", action: "Smart in practice: tire psi, stagger, ride height, one shock click \xB7 Save for qual/feature unless needed: big cross moves, wing swings, major geometry", reason: "practice guide: test the high-value levers; book race-night geometry when the track is closer to race conditions" }; var STOP_CHANGING = { lever: "practice_stop", action: "Stop changing when: two tests failed to help OR three laps within ~0.3s of your best \u2014 make clean laps, work on line and release, log what you have", reason: "practice guide: drivers improve from laps too \u2014 not every practice needs another wrench turn" }; var CLASS_NOTES = { quarter_midget: { lever: "practice_class", action: "QM practice tip: young drivers \u2014 baseline laps first, then parent picks ONE lever (usually psi or effective stagger); driver focuses on line", reason: "practice guide (QM): split roles \u2014 handler logs, driver repeats the line" }, outlaw_kart: { lever: "practice_class", action: "Outlaw practice tip: log cross + seat mm on run 1; test inch stagger or one wing step in practice \u2014 not both same session end", reason: "practice guide (outlaw): offset karts need a written cross baseline before stagger experiments" }, flat_kart: { lever: "practice_class", action: "Flat kart practice tip: log cross/left/nose + seat hole on run 1; test rear track width OR stagger in practice \u2014 one lever per session block", reason: "practice guide (flat kart): straight-rail scaling sheet before experiments \u2014 A-B-A replaces generic bands fast" }, micro_sprint_600: { lever: "practice_class", action: "600 micro practice tip: Hyper order in practice \u2014 square, psi/stagger, one RH or wing hole \u2014 save bar/shock stacks for after baseline confirmed", reason: 'practice guide (600 micro): 10" dirt \u2014 basics in practice, fine-tune in qual if needed' }, lightning_sprint: { lever: "practice_class", action: "Lightning practice tip: confirm winged vs wingless sheet on run 1; practice validates psi/stagger \u2014 Jacobs/ladder big moves wait for race line unless push is severe", reason: 'practice guide (lightning): 13" Hyper \u2014 not full sprint practice philosophy' } }; var BUCKET_ADD = { drying: { lever: "practice_cond", action: "Drying practice: re-baseline after track sheen breaks \u2014 first test is usually stagger or RR psi, not a four-corner shock session", reason: "practice guide: drying tracks change mid-practice \u2014 log track state each run" }, slick: { lever: "practice_cond", action: "Slick practice: fewer tests, more laps \u2014 one stagger or RR psi test, then line work; slick rewards repeatability", reason: "practice guide: slick nights punish setup roulette \u2014 validate one move, then drive" }, greasy: { lever: "practice_cond", action: "Greasy practice: short test plan \u2014 baseline, one RS psi or stagger test, then laps; avoid max cross + max wing in first practice runs", reason: "practice guide: heavy tracks change quickly \u2014 conservative practice plan" } }; function grassrootsPracticeStrategy(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { practicePriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; const priors = []; if (!skip.has("practice_structure")) priors.push(STRUCTURE); if (!skip.has("practice_log")) priors.push(LOG_CHECKLIST); if (!skip.has("practice_validate")) priors.push(VALIDATE_BASELINE); if (!skip.has("practice_test_vs_save")) priors.push(TEST_VS_SAVE); if (!skip.has("practice_stop")) priors.push(STOP_CHANGING); const classNote = CLASS_NOTES[profile2]; if (classNote && !skip.has("practice_class")) priors.push(classNote); const cond = BUCKET_ADD[bucket]; if (cond && !skip.has("practice_cond")) priors.push(cond); const seen = /* @__PURE__ */ new Set(); const practicePriors = priors.filter((p) => { if (seen.has(p.lever)) return false; seen.add(p.lever); return true; }).slice(0, maxPriors); return { practicePriors, surfaceBucket: bucket }; } // scripts/lib/experimental/grassrootsRaceNightStrategy.mjs var GRASSROOTS_RACE_CAVEAT = "Race-night game plan from grassroots best practices \u2014 one bad heat is not a verdict; logged findings and your baseline sheet decide changes."; var GRASSROOTS_RACE_SOURCE = "Grassroots dirt oval race night guide"; var HEAT_VS_FEATURE = { lever: "race_heat_feature", action: "Heats: learn the line, log track state + feel, protect tires \u2014 at most ONE small change if entry and exit agree \xB7 Feature: race what you confirmed \u2014 big moves only if two heats showed the same problem", reason: "race guide: heats are recon + validate; feature is execute, not experiment" }; var CHANGE_VS_WAIT = { lever: "race_change_wait", action: "Change on race night when: same symptom in two runs AND logged psi/stagger still on baseline \xB7 Wait when: one bad lap, one slow heat, or you stacked a change last run \u2014 reset and watch the next heat", reason: "race guide: reactive pits lose races \u2014 pattern beats panic" }; var DATA_DECISIONS = { lever: "race_data_quick", action: "Under pressure: open your baseline (psi, stagger, cross) \u2192 check Crew Chief ranked list \u2192 pick ONE move from handling/tire/surface layer \u2014 log it before rollout", reason: "race guide: real logged A-B-A on your car beats guessing; public baselines are the fallback when data is thin" }; var NIGHT_MANAGEMENT = { lever: "race_night_manage", action: "Manage the night: note when track goes slick (usually mid-program) \xB7 remeasure hot psi once \xB7 save energy \u2014 handler writes changes, driver stays calm between runs", reason: "race guide: tire wear and track evolution matter as much as one shock click" }; var MINDSET = { lever: "race_mindset", action: "Mindset: one bad heat \u2260 wrong setup \u2014 breathe, read notes, decide with your handler \xB7 Young drivers: focus on smooth steering and hitting marks; parents pick setup", reason: "race guide: calm teams make better last-minute calls than frantic ones" }; var CLASS_NOTES2 = { quarter_midget: { lever: "race_class", action: "QM race night: qual/heat is for line and hot psi log \u2014 feature change is usually 1 psi or effective stagger, not cross and camber same break", reason: "race guide (QM): light cars punish stacked race-night changes" }, outlaw_kart: { lever: "race_class", action: "Outlaw race night: heat confirms cross + stagger \xB7 feature wing step only if entry AND exit agreed in two runs \u2014 seat mm is a big move, not a heat reaction", reason: "race guide (outlaw): offset karts need written baseline between heats" }, flat_kart: { lever: "race_class", action: "Flat kart race night: heat confirms cross/left/nose on scales \xB7 feature move is usually seat hole, rear track 2 mm, or 1/4 psi \u2014 one lever only", reason: "race guide (flat kart): logged baseline between heats \u2014 not outlaw wing or micro blocks" }, micro_sprint_600: { lever: "race_class", action: "600 micro race night: Hyper order under pressure \u2014 psi/stagger/RH before wing or shock \xB7 drying program often wants less stagger before feature", reason: 'race guide (600 micro): 10" dirt \u2014 basics first even when the clock is loud' }, lightning_sprint: { lever: "race_class", action: "Lightning race night: confirm tacky vs slick call before feature \xB7 one Hyper lever (psi, stagger, wing hole) \u2014 not Maxim sprint shock stacks between heats", reason: 'race guide (lightning): 13" midget-scale decisions, not full sprint pit chaos' } }; var BUCKET_ADD2 = { drying: { lever: "race_cond", action: "Drying race night: track state changes heat-to-heat \u2014 if sheen broke, first feature move is often stagger or RR psi, not four corners", reason: "race guide: log drying in notes so Crew Chief surface layer matches tonight" }, slick: { lever: "race_cond", action: "Slick feature: protect RR, consider less stagger \u2014 patience beats a hero change on lap 3 of the feature", reason: "race guide: slick rewards the team that stopped changing two heats ago" }, greasy: { lever: "race_cond", action: "Greasy/heavy program: conservative feature plan \u2014 may run upper psi band; avoid max cross + max wing because heat 1 felt tight", reason: "race guide: heavy tracks punish over-reaction between heats" }, tacky: { lever: "race_cond", action: "Tacky program: hold baseline through heat 1 unless push/loose repeats \u2014 first race-night test is still one lever only", reason: "race guide: normal grip nights reward discipline over pit frenzy" } }; function grassrootsRaceNightStrategy(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { racePriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; const priors = []; if (!skip.has("race_heat_feature")) priors.push(HEAT_VS_FEATURE); if (!skip.has("race_change_wait")) priors.push(CHANGE_VS_WAIT); if (!skip.has("race_data_quick")) priors.push(DATA_DECISIONS); if (!skip.has("race_night_manage")) priors.push(NIGHT_MANAGEMENT); if (!skip.has("race_mindset")) priors.push(MINDSET); const classNote = CLASS_NOTES2[profile2]; if (classNote && !skip.has("race_class")) priors.push(classNote); const cond = BUCKET_ADD2[bucket] || BUCKET_ADD2.tacky; if (cond && !skip.has("race_cond")) priors.push(cond); const seen = /* @__PURE__ */ new Set(); const racePriors = priors.filter((p) => { if (seen.has(p.lever)) return false; seen.add(p.lever); return true; }).slice(0, maxPriors); return { racePriors, surfaceBucket: bucket }; } // scripts/lib/experimental/grassrootsLoggingGuidance.mjs var GRASSROOTS_LOGGING_CAVEAT = "Logging habits from grassroots best practices \u2014 good notes unlock Crew Chief A-B-A; memory alone cannot beat two clean runs."; var GRASSROOTS_LOGGING_SOURCE = "Grassroots dirt oval setup logging guide"; var WHAT_TO_RECORD = { lever: "log_what_record", action: "Log every run \u2014 before: cold psi (4), stagger, ride heights, cross/wing, track state \xB7 after: hot psi, lap time or heat finish, entry/mid/exit feel, exact change made (one lever)", reason: "logging guide: Crew Chief and your handler need the same snapshot \u2014 missing hot psi is the #1 reason A-B-A fails" }; var NOTEBOOK_FORMAT = { lever: "log_format", action: "Notebook that works: one row per run (date \xB7 track \xB7 tacky/slick \xB7 setup summary \xB7 change \xB7 result \xB7 feel) \u2014 or Garage Setup + session logger; photo of manufacturer sheet in phone album is fine", reason: "logging guide: paper clipboard, notes app, or this garage \u2014 pick one and stick with it" }; var QUICK_SUSTAINABLE = { lever: "log_quick", action: "Keep it quick: 60 seconds after rollout \u2014 handler writes, driver talks feel \xB7 same 5 fields every run beats a perfect log you quit after week 2", reason: "logging guide: sustainable beats complete \u2014 two lines per run still beats guessing next month" }; var WHY_ABA = { lever: "log_why_aba", action: "Why records matter: logged A-B-A on YOUR car outranks public baselines here \u2014 without notes you repeat the same wrong move; with notes you know what actually helped", reason: "logging guide: troubleshooting and improvement both start with what changed and what the track did" }; var CLASS_NOTES3 = { quarter_midget: { lever: "log_class", action: "QM log tip: note effective stagger (LR/RR) + seat mm on run 1 \u2014 young driver says push/loose in kid words; parent writes psi and change", reason: "logging guide (QM): light cars \u2014 split who drives vs who writes" }, outlaw_kart: { lever: "log_class", action: "Outlaw log tip: cross + seat + wing setting on every row \u2014 offset karts hide mistakes when cross drifts between sessions", reason: "logging guide (outlaw): cross is your anchor number" }, flat_kart: { lever: "log_class", action: "Flat kart log tip: cross/left/nose % + seat hole + rear track width + hot psi every row \u2014 your log replaces generic priors fastest", reason: "logging guide (flat kart): A-B-A notebook is the upgrade path for flat dirt oval" }, micro_sprint_600: { lever: "log_class", action: '600 micro log tip: Hyper sheet order on row 1 \u2014 psi/stagger/RH before wing or shock notes; 10" nights move fast, baseline row saves the break', reason: "logging guide (600 micro): basics column first, fine-tune column second" }, lightning_sprint: { lever: "log_class", action: 'Lightning log tip: winged vs wingless + hole count on run 1 \u2014 13" Hyper, not Maxim sprint shock diary; one lever column per run', reason: "logging guide (lightning): midget-scale log, not full sprint spreadsheet" } }; function grassrootsLoggingGuidance(trackState, profile2, opts = {}) { if (!profile2 || !isGrassrootsDayOneProfile(profile2)) { return { loggingPriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const maxPriors = opts.maxPriors ?? 3; const priors = []; if (!skip.has("log_what_record")) priors.push(WHAT_TO_RECORD); if (!skip.has("log_format")) priors.push(NOTEBOOK_FORMAT); if (!skip.has("log_quick")) priors.push(QUICK_SUSTAINABLE); if (!skip.has("log_why_aba")) priors.push(WHY_ABA); const classNote = CLASS_NOTES3[profile2]; if (classNote && !skip.has("log_class")) priors.push(classNote); const seen = /* @__PURE__ */ new Set(); const loggingPriors = priors.filter((p) => { if (seen.has(p.lever)) return false; seen.add(p.lever); return true; }).slice(0, maxPriors); return { loggingPriors, surfaceBucket: "unknown" }; } // scripts/lib/experimental/advancedWingGeometryIntegration.mjs var WING_RC_LOAD = { lever: "advanced_geo_wing_rc_load", action: "Wing load \xD7 rear RC: more wing angle increases rear downforce AND roll moment \u2014 lower rear RC (bar down) lets the chassis roll into that load (more RR bite, can tighten). Higher rear RC (bar up) resists roll \u2014 same wing angle feels freer on entry but may lack RR load on exit", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Wing and bar height move the same balance triangle \u2014 log both whenever either changes") }; var WING_ROLL_EFFECT = { lever: "advanced_geo_wing_roll_effect", action: 'Roll vs wing effectiveness: too much roll \u2192 wing loads RR early then car binds mid/exit (wing feels "used up"). Too little roll \u2192 wing adds load but tires never reach peak grip in the middle \u2014 car feels flat or pushy. Target: enough roll to feel RR load, not enough to bind drive-off', reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Phase feel tells you roll amount \u2014 log entry/mid/exit when wing or bar moves") }; var BAR_VS_WING_FIRST = { lever: "advanced_geo_bar_vs_wing_first", action: "Bar vs wing \u2014 try first: (1) scale/square drift \u2192 fix scale before bar OR wing \xB7 (2) entry push + wing already high \u2192 wing down FIRST \xB7 (3) entry push + wing moderate \u2192 raise bar or RF platform FIRST \xB7 (4) exit loose + wing moderate \u2192 bar down or stagger BEFORE wing add \xB7 (5) tight everywhere + low bar + high wing \u2192 raise bar OR wing down before more bite", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Decision order prevents stacking load on an already overloaded RC platform") }; var BAR_VS_WING_ENTRY = { lever: "advanced_geo_bar_vs_wing_entry", action: "Entry phase: push on entry \u2192 if wing \u2265 recent baseline, wing down 0.5\xB0 OR bar up one hole (less roll into load) \xB7 entry free/loose \u2192 bar down one hole OR wing up 0.5\xB0 \xB7 never both bar and wing same run \u2014 log which you chose", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Entry is where RC + wing load first meet \u2014 isolate one lever") }; var BAR_VS_WING_EXIT = { lever: "advanced_geo_bar_vs_wing_exit", action: "Exit phase: loose off \u2192 LR bite first (bar down, stagger, or RR platform) before wing forward/angle add \xB7 tight/bind off \u2192 bar up OR wing down before stiffening shocks \xB7 if wing helped entry but hurt exit, roll likely excessive for tonight's grip \u2014 raise bar before adding wing", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Exit fixes are usually RC/stagger; wing is secondary unless aero balance is clearly off") }; var WING_BIG_TRACK_STRATEGY = { lever: "advanced_geo_wing_big_track_rc", action: "Big track: lower wing angle + forward wing position reduce drag \u2014 rear RC can stay slightly higher (less roll) because corners are long and speed loads RR. If mid-corner push appears as track frees, wing down before second bar turn \u2014 low RC + high wing stacks load in long corners", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Big-track strategy pairs aero efficiency with stable RC \u2014 not max wing + min bar") }; var WING_SMALL_TRACK_STRATEGY = { lever: "advanced_geo_wing_small_track_rc", action: "Small track: wing angle often higher for rotation \u2014 rear RC must support it (bar height set first, then wing step). If car rotates on entry but snaps loose off, RC roll is likely too high for the wing load \u2014 raise bar one hole before removing wing", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Short tracks need RC platform before wing hero \u2014 wing without RC support feels inconsistent") }; var WING_MIDGET_RC = { lever: "advanced_geo_wing_midget_rc", action: "Midget wing \xD7 RC: lighter car, shorter wheelbase \u2014 panhard height sets LR platform before wing moves \xB7 winged midget: wing is entry knob but bar height still sets how much roll the wing can use \xB7 log panhard + wing together every scale", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Midget aero load is smaller but phase window is shorter \u2014 RC errors show up faster") }; function normalizeTrack9(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy", "drying"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; return "tacky"; } function advancedWingGeometryPriors(trackState, opts = {}) { const max = opts.maxPriors ?? 6; const profile2 = opts.profile || "maxim_sprint_winged"; const isMidget = profile2 === "hyper_midget"; const track = normalizeTrack9(trackState); const priors = [ WING_RC_LOAD, WING_ROLL_EFFECT, BAR_VS_WING_FIRST, BAR_VS_WING_ENTRY, BAR_VS_WING_EXIT, isMidget ? WING_MIDGET_RC : track === "slick" ? WING_BIG_TRACK_STRATEGY : WING_SMALL_TRACK_STRATEGY ]; return withAdvancedCaveat(priors, ADVANCED_CHASSIS_CAVEAT).slice(0, max); } // scripts/lib/experimental/advancedSprintGeometryPrinciples.mjs var ADVANCED_GEOMETRY_SOURCE = "Sprint/midget geometry principles (roll center \xB7 panhard/J-bar \xB7 wing load \u2014 industry reference, not brand numbers)"; var GEO_ROLL_CENTER = { lever: "advanced_geo_roll_center", action: "Roll axis: front and rear roll centers define how the car transfers weight in roll. Tune RC interaction before stacking bar rate, shock, or wing \u2014 log which phase (entry/mid/exit) changes when you move rear RC.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Industry sprint framework \u2014 RC height and lateral position set the platform competitive teams build on") }; var GEO_REAR_ROLL_CENTER = { lever: "advanced_geo_rear_roll_center", action: "Rear RC via panhard/J-bar: lower bar \u2192 lower rear RC \u2192 more chassis roll and RR load (often tightens). Higher bar \u2192 less roll, quicker steering (can free entry). Most teams index from axle centerline and move one step per run with scale notes.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Rear RC is the primary sprint dirt tuning axis \u2014 document height/hole on every scale") }; var GEO_MOMENT_IC = { lever: "advanced_geo_moment_ic", action: "Instant centers: trailing arms and radius rods set theoretical IC points that work with RC height. Before a bar or shock chase, verify birdcage timing and arm length \u2014 a hole change shifts IC and can mimic a handling problem.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Link geometry sets roll resistance \u2014 IC moves when arms or birdcage index change") }; var GEO_BALANCED_ROLL = { lever: "advanced_geo_balanced_roll", action: "Roll balance: aim for controlled roll front-to-rear \u2014 enough for the driver to feel load, not enough to bind exit. If the car rolls but does not forward-drive, fix platform (RC + cross) before adding bite elsewhere.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Competitive teams pair RC with spring/torsion, shock, wing, and weight \u2014 one lever per A-B-A") }; var GEO_TRADEOFFS = { lever: "advanced_geo_tradeoffs", action: "RC trade-offs at the track: lower RC buys side bite but adds body roll (can hurt exit). Higher RC frees entry but can reduce RR load. Use phase feel \u2014 entry push after lowering bar often means too much roll or wing load, not always \u201Cmore front.\u201D", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Directional diagnosis beats guessing RC \u2014 log bar height with entry/mid/exit notes") }; var GEO_PANHARD_HEIGHT = { lever: "advanced_geo_panhard_height", action: "Panhard/J-bar height (primary dial): log hole or inches on the scale sheet every move. Lower for bite/tighten \xB7 higher for less roll/loosen entry. Pair with spring/torsion or stagger plan \u2014 height rarely works alone on a competitive night.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Height is the in-season RC tool most teams actually turn \u2014 compare nights only with matched tires and fuel") }; var GEO_PANHARD_ANGLE = { lever: "advanced_geo_panhard_angle", action: "Bar angle: slight rise from RR (axle side) to LR (chassis side) is common on dirt \u2014 loads RR progressively through travel. Angle + height change roll steer together; record both on the scale sheet before heat two.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Angle affects how RR loads in roll \u2014 baseline angle is part of a repeatable scale reference") }; var GEO_PANHARD_MOUNT = { lever: "advanced_geo_panhard_mount", action: "Mount location: left-side chassis mount is standard on dirt; right-side or bar ahead/behind axle changes roll-steer character. Log mount when experimenting \u2014 validate with your builder before copying another team's layout.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Mount position changes RC migration through suspension travel") }; var GEO_PANHARD_LENGTH = { lever: "advanced_geo_panhard_length", action: "Bar length: longer bars reduce bind and keep RC movement predictable over bumps; shorter bars feel sharper. Length is usually fixed by builder \u2014 height and angle are the weekly tuning levers.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Length is design-level; height/angle are session-level") }; var GEO_WING_MULTIPLIER = { lever: "advanced_geo_wing_multiplier", action: "Wing \xD7 geometry: wing angle adds rear load and bite but increases roll moment \u2014 a low RC car with more wing can tighten abruptly. Compensation rule: wing step OR bar height step per run; if you add wing, note whether bar or spring softens on the next run.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Treat wing as a geometry multiplier \u2014 log wing angle with bar height and cross every session") }; var GEO_WING_BIG_TRACK = { lever: "advanced_geo_wing_big_track", action: "Big track / slick: less wing angle and more forward wing position reduce drag and keep RC platform stable in long corners. If the car pushes mid-corner on a freed track, wing down before a second bar turn often isolates the lever.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Big tracks punish stacked wing + low RC \u2014 plan feature moves from heat scale drift") }; var GEO_WING_SMALL_TRACK = { lever: "advanced_geo_wing_small_track", action: "Small track / tacky: more wing angle helps rotation and RR load on tight bullrings \u2014 RC (bar height + IC) must carry the extra load without entry push. One wing step OR one bar hole per run, not both in the same heat.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Short tracks reward wing + RC balance \u2014 phase log tells you which lever failed") }; var GEO_MIDGET_PANHARD = { lever: "advanced_geo_midget_panhard", action: "Midget panhard/J-bar: same RC logic, shorter wheelbase \u2014 phase errors show up faster. Set LR platform (bar height + left-side %) before wing or stagger moves on USAC/POWRi dirt; log panhard with cross every scale.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Midget cross and left-side bands differ from 410 \u2014 platform first, aero second") }; var GEO_SYSTEMS = { lever: "advanced_geo_systems", action: "Systems check before the feature: RC/panhard + spring or torsion rate + shock platform + wing angle/position + weight/CG. Competitive cars rarely move one in isolation \u2014 your scale sheet should show what moved last.", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Logged scale + bar + wing + phase feel is the working framework at this level") }; var GEO_TRACK_WORKFLOW = { lever: "advanced_geo_track_workflow", action: "Pit workflow: scale \u2192 log bar height/angle + wing + cross \u2192 one structured run \u2192 same line \u2192 note entry/mid/exit \u2192 one lever \u2192 repeat. That sequence is how builder shops validate geometry changes between sessions.", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Repeatable scale + A-B-A discipline separates reference notes from car-specific truth") }; function normalizeTrack10(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (["greasy", "wet"].includes(s)) return "wet"; if (["tacky", "rubbered", "drying"].includes(s)) return "tacky"; return "unknown"; } function advancedSprintGeometryPriors(trackState, opts = {}) { const max = opts.maxPriors ?? 10; const profile2 = opts.profile || "maxim_sprint_winged"; const isMidget = profile2 === "hyper_midget"; const track = normalizeTrack10(trackState); const core = [ GEO_TRACK_WORKFLOW, GEO_REAR_ROLL_CENTER, isMidget ? GEO_MIDGET_PANHARD : GEO_PANHARD_HEIGHT, GEO_PANHARD_ANGLE, GEO_WING_MULTIPLIER, track === "slick" || track === "drying" ? GEO_WING_BIG_TRACK : GEO_WING_SMALL_TRACK, GEO_SYSTEMS ]; const wingDeep = advancedWingGeometryPriors(trackState, { profile: profile2, maxPriors: 5 }); const extended = [ GEO_ROLL_CENTER, GEO_BALANCED_ROLL, GEO_TRADEOFFS, GEO_MOMENT_IC, GEO_PANHARD_MOUNT, GEO_PANHARD_LENGTH ]; const used = /* @__PURE__ */ new Set(); const priors = []; for (const p of [...core, ...wingDeep, ...extended]) { if (used.has(p.lever)) continue; used.add(p.lever); priors.push(p); } return withAdvancedCaveat(priors, ADVANCED_CHASSIS_CAVEAT).slice(0, max); } function pickAdvancedGeometryBriefLine(profile2 = "maxim_sprint_winged") { const isMidget = profile2 === "hyper_midget"; if (isMidget) { return "Midget platform: panhard/J-bar sets LR bite; wing scales load \u2014 log bar height, angle, and wing together"; } return "Sprint platform: rear RC via panhard/J-bar; wing scales load \u2014 log bar height, angle, and wing together"; } // scripts/lib/experimental/advancedChassisLoggingGuidance.mjs var ADVANCED_LOGGING_SOURCE = "Advanced sprint/midget logging workflow (scale \xB7 bar \xB7 wing \xB7 phase feel \xB7 A-B-A)"; var ADVANCED_LOGGING_CAVEAT = "Logging discipline is how reference notes become car-specific recommendations."; var LOG_MINIMUM = { lever: "advanced_log_minimum", action: "Minimum log every run: scale (LF/RF/LR/RR psi or hot numbers) \xB7 LR/RR bar hole or inches + angle \xB7 wing angle + fore/aft position \xB7 track state \xB7 entry/mid/exit feel in one line each", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Without this baseline row, geometry and wing notes stay generic \u2014 your log is the upgrade path") }; var LOG_COMPENSATION = { lever: "advanced_log_compensation", action: 'Log compensation pairs explicitly: e.g. "J-bar +1 hole \u2192 wing \u22120.5\xB0" or "wing +1\xB0 \u2192 LR bar \u22121 turn" \xB7 note which lever you changed FIRST and what you paired on the next run', reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Paired moves without notation look like noise \u2014 Crew Chief needs cause \u2192 effect chains") }; var LOG_ABA_GEOMETRY = { lever: "advanced_log_aba_geometry", action: "Geometry/wing A-B-A: Run A = baseline scale + 3 laps same line \xB7 change ONE of (bar height, bar angle, wing angle) \xB7 Run B = same line, same pace \xB7 log phase delta \xB7 revert or keep for Run C only after B is clear", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "One geometry or wing variable per A-B-A \u2014 stacking bar + wing same run invalidates the log") }; var LOG_ABA_WING = { lever: "advanced_log_aba_wing", action: "Wing A-B-A: hold bar height/angle fixed \xB7 wing step 0.5\u20131\xB0 \xB7 log entry push/free and exit drive \xB7 if wing helped entry but hurt exit, note roll amount \u2014 excessive roll often means RC could not support the added load", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Wing tests require stable RC platform \u2014 log bar position on every wing A-B-A") }; var LOG_SESSION_HEADER = { lever: "advanced_log_session", action: 'Session header (copy each night): builder/card ref \xB7 tire circumference \xB7 fuel load \xB7 track size (big/small) \xB7 heat number \xB7 "baseline" vs "feature" scale row', reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Headers let you compare nights \u2014 mismatched fuel or tires invalidate cross-session reads") }; var LOG_INCONSISTENT = { lever: "advanced_log_inconsistent", action: "If feel is inconsistent lap-to-lap: log tire hot psi lap 8\u201310, bar position, and wing BEFORE another change \xB7 inconsistent often = platform drift (scale/circumference) not wrong hero bite", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Fix repeatability in the log before chasing speed") }; function advancedChassisLoggingPriors(trackState, enrichedVc = {}, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const max = opts.maxPriors ?? 4; const priors = [ LOG_MINIMUM, LOG_COMPENSATION, LOG_ABA_GEOMETRY, LOG_ABA_WING, LOG_SESSION_HEADER, LOG_INCONSISTENT ].filter((p) => !skip.has(p.lever)); return withAdvancedCaveat(priors, ADVANCED_LOGGING_CAVEAT).slice(0, max); } function pickAdvancedLoggingBriefLine() { return "Log every run: scale + bar hole/angle + wing + track state + entry/mid/exit \u2014 one lever per A-B-A"; } // scripts/lib/experimental/pivotTiers.mjs var PIVOT_TIER = { FULL: 1, FOCUSED: 2, MINIMAL: 3 }; var PIVOT_TIER_LABELS = { 1: "Full", 2: "Focused", 3: "Minimal" }; var FOCUSED_REF_DEFS = { micro_sprint_600: [ { key: "j_ladder", label: "J-ladder / J-bar position", fields: ["j_ladder"] }, { key: "panhard", label: "Panhard height", fields: ["panhard"] }, { key: "lr_trailing", label: "LR trailing arm / birdcage ref", fields: ["lr_trailing_ref", "pivot_pickup_notes"] }, { key: "front_ref", label: "Front arm / torsion reference", fields: ["front_susp_ref", "bite"] } ], lightning_sprint: [ { key: "j_ladder", label: "J-bar / ladder position", fields: ["j_ladder"] }, { key: "panhard", label: "Panhard height", fields: ["panhard"] }, { key: "lr_trailing", label: "LR trailing arm ref", fields: ["lr_trailing_ref", "pivot_pickup_notes"] }, { key: "wing_angle", label: "Wing angle baseline", fields: ["wing_angle"], wingedOnly: true } ], jrsprint: [ { key: "panhard", label: "Panhard / bar height", fields: ["panhard"] }, { key: "j_ladder", label: "J-bar position", fields: ["j_ladder"] }, { key: "lr_trailing", label: "Rear pickup notes", fields: ["pivot_pickup_notes"] } ], micro_sprint_270: [ { key: "j_ladder", label: "J-ladder / J-bar position", fields: ["j_ladder"] }, { key: "panhard", label: "Panhard height", fields: ["panhard"] }, { key: "lr_trailing", label: "LR trailing arm / birdcage ref", fields: ["lr_trailing_ref", "pivot_pickup_notes"] }, { key: "front_ref", label: "Front arm / torsion reference", fields: ["front_susp_ref", "bite"] } ], micro_turf: [ { key: "compound", label: "Turf compound / psi baseline", fields: ["pivot_pickup_notes", "bite"] }, { key: "rear_ref", label: "Rear linkage reference", fields: ["lr_trailing_ref", "panhard"] } ], default_focused: [ { key: "panhard", label: "Panhard / bar height", fields: ["panhard"] }, { key: "j_ladder", label: "J-bar / ladder position", fields: ["j_ladder"] }, { key: "rear_ref", label: "Rear linkage reference", fields: ["pivot_pickup_notes", "lr_trailing_ref"] } ] }; var FOCUSED_MODIFICATION_OPTIONS = [ { id: "coilover_conversion", label: "Coilover or front-end conversion" }, { id: "shock_tab_relocation", label: "Shock mount moved" }, { id: "trailing_arm_length", label: "Trailing arm length changed" }, { id: "panhard_jbar_mount_moved", label: "Panhard / J-bar mount moved" }, { id: "other", label: "Other structural change" } ]; function norm2(s) { return String(s || "").toLowerCase(); } function resolvePivotCarType(vc2 = {}) { const cls = norm2(vc2.car_class || vc2.class); const ct = norm2(vc2.car_type); if (/quarter.midget|qma|\bqm\b/.test(cls) || ct === "quartermidget") return "quartermidget"; if (/lightning|glls|great lakes lightning/.test(cls) || ct === "lightningsprint") return "lightningsprint"; if (/turf.*micro|micro.*turf|turf tire micro|turf sprint/.test(cls) || ct === "microturf") return "microturf"; if (/\b270\b|\b250\b/.test(cls) && /micro|sprint/.test(cls)) return "micro270"; if (/junior sprint|\bjr\.?\s*sprint\b/.test(cls) || ct === "jrsprint") return "jrsprint"; if (/mod lite|modlite|600 mini mod|mini modified/.test(cls) || ct === "modlite") return "modlite"; if (/micro|600|now600|restricted micro/.test(cls) || ct === "micro") return "micro"; if (/outlaw/.test(cls) || ct === "outlawkart") return "outlawkart"; if (/pro clone|flat kart|animal\b/.test(cls) || ct === "flatkart") return "flatkart"; if (/kart|lo.?206|clone|206|shifter|tag/.test(cls) || ct === "kart" || ct === "roadkart") return "kart"; if (/sprint|midget|305|360|410|usac/.test(cls) && !/micro|lightning|junior/.test(cls)) return "sprint"; if (/late.model|super.late|crate/.test(cls) || ct === "latemodel") return "latemodel"; if (/modified|sport.mod|b.mod/.test(cls) || ct === "modified") return "modified"; if (/hobby.stock|pure.stock|street.stock|compact/.test(cls) || ct === "stock") return "stock"; return ct || "generic"; } function resolvePivotGrassrootsProfile(vc2 = {}) { const cls = norm2(vc2.car_class || vc2.class); const ct = resolvePivotCarType(vc2); if (ct === "quartermidget" || /quarter.midget|qma|\bqm\b/.test(cls)) return "quarter_midget"; if (ct === "outlawkart" || /outlaw/.test(cls)) return "outlaw_kart"; if (ct === "flatkart" || /flat kart|pro clone/.test(cls)) return "flat_kart"; if (ct === "jrsprint" || /junior sprint|\bjr\.?\s*sprint\b/.test(cls)) return "junior_sprint"; if (ct === "microturf" || /turf.*micro|micro.*turf/.test(cls)) return "micro_turf"; if (ct === "micro270" || /\b270\b|\b250\b/.test(cls) && /micro|sprint/.test(cls)) return "micro_sprint_270"; if (ct === "micro" || /600|now600|restricted micro/.test(cls)) return "micro_sprint_600"; if (ct === "lightningsprint" || /lightning|glls/.test(cls)) return "lightning_sprint"; if (ct === "kart") return "flat_kart"; return null; } function resolvePivotTier(vc2 = {}, profile2 = "") { const ct = resolvePivotCarType(vc2); const cls = norm2(vc2.car_class || vc2.class); const grassroots = resolvePivotGrassrootsProfile(vc2); if (isAdvancedChassisProfile(profile2) || isLateModelEvolutionProfile(profile2)) { return tierMeta(PIVOT_TIER.FULL, "full", ct, grassroots, "Full PIVOT \u2014 measured pickups anchor RC and bar/wing order"); } if (ct === "sprint" || ct === "latemodel" || ct === "modified") { return tierMeta(PIVOT_TIER.FULL, "full", ct, grassroots, "Full PIVOT \u2014 measured pickups anchor RC and bar/wing order"); } if (ct === "stock" && /sport|limited|crate|modified|late/.test(cls)) { return tierMeta(PIVOT_TIER.FULL, "full", ct, grassroots); } if (ct === "micro" || grassroots === "micro_sprint_600") { return tierMeta( PIVOT_TIER.FOCUSED, "focused", ct, "micro_sprint_600", "Focused PIVOT \u2014 log J-ladder, panhard, and rear refs (no full coordinate wizard)" ); } if (ct === "micro270" || grassroots === "micro_sprint_270") { return tierMeta( PIVOT_TIER.FOCUSED, "focused", ct, "micro_sprint_270", "Focused PIVOT \u2014 270/250 micro linkage refs (not 600cc hole chart)" ); } if (ct === "microturf" || grassroots === "micro_turf") { return tierMeta( PIVOT_TIER.FOCUSED, "focused", ct, "micro_turf", "Focused PIVOT \u2014 turf compound + rear refs (not dirt D-series workflow)" ); } if (ct === "lightningsprint" || grassroots === "lightning_sprint") { return tierMeta( PIVOT_TIER.FOCUSED, "focused", ct, "lightning_sprint", "Focused PIVOT \u2014 midget-scale refs, not full sprint coordinate wizard" ); } if (ct === "jrsprint" || grassroots === "junior_sprint") { return tierMeta( PIVOT_TIER.FOCUSED, "focused", ct, "junior_sprint", 'Focused PIVOT \u2014 Jr Sprint 204cc / 8" tire platform refs' ); } if (ct === "modlite") { return tierMeta( PIVOT_TIER.FOCUSED, "focused", ct, "mod_lite", "Focused PIVOT \u2014 mod lite panhard/cross refs (not 600 micro or A-mod sheet)" ); } if (ct === "quartermidget" || grassroots === "quarter_midget") { return tierMeta(PIVOT_TIER.MINIMAL, "minimal", ct, "quarter_midget", "Scale + ride height drive setup \u2014 PIVOT not required for QM"); } if (ct === "outlawkart" || ct === "flatkart" || ct === "kart" || ct === "roadkart") { return tierMeta(PIVOT_TIER.MINIMAL, "minimal", ct, grassroots || "flat_kart", "Seat mm + cross + stagger \u2014 no coordinate PIVOT for this class"); } if (isGrassrootsDayOneProfile(grassroots)) { return tierMeta(PIVOT_TIER.MINIMAL, "minimal", ct, grassroots); } return tierMeta(PIVOT_TIER.MINIMAL, "minimal", ct, grassroots, "Log scale and ride height \u2014 reference priors until you add session rows"); } function tierMeta(tier, id, carType, grassrootsProfile, hint = "") { return { tier, id, label: PIVOT_TIER_LABELS[tier] || "Minimal", carType, grassrootsProfile, hint, features: pivotTierFeatures(tier) }; } function pivotTierFeatures(tier) { switch (tier) { case PIVOT_TIER.FULL: return { xyzWizard: true, shockMounts: true, platformIntegrity: true, platformFull: true, staleness: true, birdcageQuestion: true, focusedRefs: false, crewChiefGeometry: "full", showPivotCoach: true, showPlatformModal: true }; case PIVOT_TIER.FOCUSED: return { xyzWizard: false, shockMounts: false, platformIntegrity: true, platformFull: false, staleness: "major_only", birdcageQuestion: false, focusedRefs: true, crewChiefGeometry: "awareness", showPivotCoach: true, showPlatformModal: true }; default: return { xyzWizard: false, shockMounts: false, platformIntegrity: false, platformFull: false, staleness: false, birdcageQuestion: false, focusedRefs: false, crewChiefGeometry: "none", showPivotCoach: false, showPlatformModal: false }; } } function fieldFilled(vc2, field) { const refs = vc2.geometry_state?.pickup_refs || {}; if (refs[field] != null && String(refs[field]).trim() !== "") return true; const top = vc2[field]; if (top != null && String(top).trim() !== "") return true; const m = vc2.setup_measurements || {}; if (m[field] != null && String(m[field]).trim() !== "") return true; if (field === "pivot_pickup_notes" && top && String(top).trim().length >= 8) return true; if (field === "pivot_pickup_notes" && m.pivot_pickup_notes && String(m.pivot_pickup_notes).trim().length >= 8) return true; return false; } function refDefsForTier(tierInfo) { if (tierInfo.grassrootsProfile === "micro_sprint_600") return FOCUSED_REF_DEFS.micro_sprint_600; if (tierInfo.grassrootsProfile === "micro_sprint_270") return FOCUSED_REF_DEFS.micro_sprint_270; if (tierInfo.grassrootsProfile === "micro_turf") return FOCUSED_REF_DEFS.micro_turf; if (tierInfo.grassrootsProfile === "junior_sprint") return FOCUSED_REF_DEFS.jrsprint; if (tierInfo.grassrootsProfile === "lightning_sprint") return FOCUSED_REF_DEFS.lightning_sprint; if (tierInfo.carType === "jrsprint") return FOCUSED_REF_DEFS.jrsprint; return FOCUSED_REF_DEFS.default_focused; } function assessFocusedPivotCompleteness(vc2 = {}, tierInfo = {}) { const defs = refDefsForTier(tierInfo); const winged = vc2.winged === true || /winged|\bwing\b/i.test(String(vc2.car_class || "")); const measured = []; const missing = []; for (const def of defs) { if (def.wingedOnly && !winged) continue; const filled = def.fields.some((f) => fieldFilled(vc2, f)); if (filled) measured.push(def.key); else missing.push(def.key); } const expected = defs.filter((d) => !(d.wingedOnly && !winged)).length; const ratio = expected ? measured.length / expected : 0; let trustLevel = "reference_only"; if (ratio >= 0.75) trustLevel = "builder_reference"; if (ratio >= 1) trustLevel = "refs_complete"; const headline = `PIVOT: ${measured.length}/${expected} geometry refs logged`; let coachLine = headline; if (ratio >= 1) { coachLine = `PIVOT: ${measured.length}/${expected} refs on file \u2014 builder guidance can use your linkage baseline`; } else if (ratio >= 0.5) { coachLine = `PIVOT: ${measured.length}/${expected} refs \u2014 finish J-bar/panhard/rear for sharper micro/lightning recs`; } else { coachLine = `PIVOT: log J-ladder + panhard + rear refs (Setup \u2192 Pickups) \u2014 no coordinate wizard needed`; } let completionBenefit = null; if (ratio >= 1) { completionBenefit = "Builder and class priors now reference your logged linkage positions."; } else if (measured.length > 0) { completionBenefit = "Each ref you log beats generic hole charts for your chassis."; } return { tier: PIVOT_TIER.FOCUSED, expected, measured: measured.length, measuredKeys: measured, missing, ratio, stale: false, complete: ratio >= 1, fresh: true, trustLevel, headline, coachLine, completionBenefit, mode: "focused_refs" }; } function buildPivotCoachPayload(vc2 = {}, profile2 = "") { const tierInfo = resolvePivotTier(vc2, profile2); if (tierInfo.tier === PIVOT_TIER.MINIMAL) { return { active: false, tierInfo, coachLine: tierInfo.hint || "Scale + ride height \u2014 no PIVOT wizard for this class", showPivotButton: false, showPlatformButton: false }; } return { active: true, tierInfo, showPivotButton: tierInfo.features.xyzWizard, showFocusedButton: tierInfo.features.focusedRefs, showPlatformButton: tierInfo.features.platformIntegrity }; } // scripts/lib/experimental/platformIntegrity.mjs var DELIVERY_STATUS = { STOCK: "stock", MINOR: "minor", SIGNIFICANT: "significant", MAJOR: "major_conversion" }; var MODIFICATION_OPTIONS = [ { id: "coilover_conversion", label: "Full or partial coilover conversion" }, { id: "shock_tab_relocation", label: "Shock mount / tab relocated" }, { id: "birdcage_reindex", label: "Birdcage re-indexed or swapped" }, { id: "trailing_arm_length", label: "Trailing arm length changed" }, { id: "panhard_jbar_mount_moved", label: "Panhard / J-bar mount moved" }, { id: "multi_shock_package", label: "Multi-shock rear package added/changed" }, { id: "other", label: "Other structural change" } ]; var STALE_TRIGGER_MODS = /* @__PURE__ */ new Set([ "coilover_conversion", "shock_tab_relocation", "birdcage_reindex", "trailing_arm_length", "panhard_jbar_mount_moved", "multi_shock_package" ]); var PICKUP_LABELS = { lf_upper_inner: "LF upper A-arm inner", rf_upper_inner: "RF upper A-arm inner", lf_lower_inner: "LF lower A-arm inner", rf_lower_inner: "RF lower A-arm inner", panhard_frame: "Panhard frame mount", j_bar_frame: "J-bar frame mount", lr_trailing_arm: "LR trailing arm inner", rr_trailing_arm: "RR trailing arm inner", lr_torsion_stop: "LR torsion stop", rr_torsion_stop: "RR torsion stop", lf_shock_mount: "LF shock mount", rf_shock_mount: "RF shock mount", lr_shock_mount: "LR shock mount", rr_shock_mount: "RR shock mount", lf_coil_mount: "LF coil-over mount", lf_upper_control: "LF upper control arm inner", rf_upper_control: "RF upper control arm inner", ...LM_MOD_PICKUP_LABELS }; function expectedPickupKeysForCar(carType, frontSusp = "torsion", opts = {}) { const tierInfo = opts.tierInfo || resolvePivotTier({ car_type: carType, front_susp: frontSusp }); if (tierInfo.tier !== PIVOT_TIER.FULL) return []; const ct = String(carType || "generic").toLowerCase(); if (ct === "sprint" || ct === "jrsprint" || /midget|lightning/.test(ct)) { const base = [ "lf_upper_inner", "rf_upper_inner", "lf_lower_inner", "rf_lower_inner", "panhard_frame", "j_bar_frame", "lr_trailing_arm", "rr_trailing_arm", "lr_torsion_stop", "rr_torsion_stop", "lf_shock_mount", "rf_shock_mount", "lr_shock_mount", "rr_shock_mount" ]; if (frontSusp === "coil" || frontSusp === "full_coil") base.push("lf_coil_mount"); return base; } if (ct === "latemodel" || ct === "modified") { return expectedLmModPickupKeys(opts.vc || {}, ct) || [ "panhard_frame", "j_bar_frame", "lr_trailing_arm", "rr_trailing_arm", "lr_birdcage_outer", "rr_birdcage_outer", "lf_upper_inner", "rf_upper_inner", "lf_shock_mount", "rf_shock_mount", "lr_shock_mount", "rr_shock_mount" ]; } if (ct === "stock") { return [ "panhard_frame", "j_bar_frame", "lr_trailing_arm", "rr_trailing_arm", "lf_upper_inner", "rf_upper_inner", "lf_shock_mount", "rf_shock_mount", "lr_shock_mount", "rr_shock_mount" ]; } return [ "panhard_frame", "lf_upper_inner", "rf_upper_inner", "lr_trailing_arm", "rr_trailing_arm", "lf_shock_mount", "rf_shock_mount", "lr_shock_mount", "rr_shock_mount" ]; } function countMeasuredPickups(pickupPoints = {}) { let n = 0; for (const [k, v] of Object.entries(pickupPoints)) { if (k === "convention" || k === "status" || k.endsWith("_saved_at")) continue; if (v && typeof v === "object" && v.x != null) n += 1; } return n; } function normalizePlatformIntegrity(raw = {}) { const pi = raw && typeof raw === "object" ? raw : {}; return { version: 1, delivery_status: pi.delivery_status || null, modifications: Array.isArray(pi.modifications) ? pi.modifications.slice() : [], birdcage_reindexed: pi.birdcage_reindexed === true, multi_shock_rear: pi.multi_shock_rear || null, compact_trailing_arm: pi.compact_trailing_arm || null, other_notes: pi.other_notes || null, updated_at: pi.updated_at || null, pickups_stale: pi.pickups_stale === true, stale_reason: pi.stale_reason || null, stale_since: pi.stale_since || null }; } function modificationsShouldStalePickups(modifications = [], deliveryStatus = null) { if (deliveryStatus === DELIVERY_STATUS.MAJOR) return true; if (deliveryStatus === DELIVERY_STATUS.SIGNIFICANT) return true; return modifications.some((m) => STALE_TRIGGER_MODS.has(m)); } function markGeometryPickupsStale(geometryState = {}, reason = "Platform changed") { const gs = { ...geometryState, version: geometryState.version || 1 }; gs.pickup_points = { ...gs.pickup_points || {} }; gs.pickup_points.status = "stale"; gs.pickup_points.stale_reason = reason; gs.pickup_points.stale_since = (/* @__PURE__ */ new Date()).toISOString(); return gs; } function clearPickupStaleFlag(geometryState = {}) { const gs = { ...geometryState, version: geometryState.version || 1 }; gs.pickup_points = { ...gs.pickup_points || {} }; if (gs.pickup_points.status === "stale") { gs.pickup_points.status = "in_progress"; delete gs.pickup_points.stale_reason; delete gs.pickup_points.stale_since; } return gs; } function assessPivotCompleteness(vc2 = {}, carType, profile2 = "") { const ct = carType || vc2.car_type || resolvePivotCarType(vc2); const tierInfo = resolvePivotTier({ ...vc2, car_type: ct }, profile2); if (tierInfo.tier === PIVOT_TIER.MINIMAL) { return { tier: PIVOT_TIER.MINIMAL, expected: 0, measured: 0, measuredKeys: [], missing: [], missingLabels: [], missingShocks: [], ratio: 0, stale: false, complete: false, fresh: true, trustLevel: "reference_only", headline: "PIVOT: not used for this class \u2014 scale + ride height drive recs", coachLine: tierInfo.hint || "Log scale and ride height \u2014 reference priors apply", completionBenefit: null, platformIntegrity: normalizePlatformIntegrity(vc2.platform_integrity), tierInfo, mode: "minimal" }; } if (tierInfo.tier === PIVOT_TIER.FOCUSED) { const focused = assessFocusedPivotCompleteness(vc2, tierInfo); const pi2 = normalizePlatformIntegrity(vc2.platform_integrity || vc2.geometry_state?.platform_integrity); if (pi2.pickups_stale) { focused.stale = true; focused.trustLevel = "reference_only"; focused.coachLine = "Platform changed \u2014 re-log J-bar/panhard/rear refs before trusting geometry notes"; focused.headline += " \xB7 STALE"; } return { ...focused, platformIntegrity: pi2, tierInfo }; } const frontSusp = vc2.front_susp || vc2.setup_style || "torsion"; const expectedKeys = expectedPickupKeysForCar(ct, frontSusp, { tierInfo, vc: vc2 }); const pp = vc2.geometry_state?.pickup_points || {}; const pi = normalizePlatformIntegrity(vc2.platform_integrity || vc2.geometry_state?.platform_integrity); const stale = pi.pickups_stale || pp.status === "stale"; let measured = []; let missing = []; let ratio = 0; let missingShocks = []; let optionalMissing = []; if (isLmModPivotClass(ct)) { const scored = scoreLmModPickups(pp, vc2); measured = scored.measured; missing = scored.missing; ratio = scored.ratio; optionalMissing = scored.optionalMissing; missingShocks = missing.filter((k) => /shock_mount|secondary_shock/.test(k)); } else { for (const key of expectedKeys) { const pt = pp[key]; if (pt && typeof pt === "object" && pt.x != null) measured.push(key); else missing.push(key); } ratio = expectedKeys.length ? measured.length / expectedKeys.length : 0; const shockKeys = ["lf_shock_mount", "rf_shock_mount", "lr_shock_mount", "rr_shock_mount"]; missingShocks = shockKeys.filter((k) => expectedKeys.includes(k) && missing.includes(k)); } const expectedCount = isLmModPivotClass(ct) ? scoreLmModPickups(pp, vc2).required.length : expectedKeys.length; let trustLevel = "reference_only"; if (stale) { trustLevel = "reference_only"; } else if (ratio >= 0.85 && missingShocks.length === 0) { trustLevel = "car_anchored"; } else if (ratio >= 0.55) { trustLevel = "builder_reference"; } const missingLabels = missing.slice(0, 4).map((k) => PICKUP_LABELS[k] || k.replace(/_/g, " ")); let headline = `PIVOT: ${measured.length}/${expectedCount} pickups measured`; if (missingShocks.length) headline += ` (${missingShocks.length} shock mount${missingShocks.length > 1 ? "s" : ""} missing)`; if (optionalMissing.length && hasMultiShockRear(vc2)) headline += ` \xB7 ${optionalMissing.length} secondary/can ref${optionalMissing.length > 1 ? "s" : ""} optional`; if (stale) headline += " \xB7 STALE \u2014 re-measure after platform change"; let coachLine = headline; if (isLmModPivotClass(ct)) { coachLine = lmModPivotCoachLine({ measured: measured.length, expected: expectedCount, missing, missingShocks, optionalMissing, stale, complete: ratio >= 0.85 && missingShocks.length === 0 && !stale, coachLine: headline }, vc2); } else if (trustLevel === "car_anchored") { coachLine = `PIVOT: ${measured.length}/${expectedCount} on file \u2014 RC comparisons anchored to your car`; } else if (trustLevel === "builder_reference") { coachLine = `PIVOT: ${measured.length}/${expectedCount} \u2014 finish shock mounts + rear inners for car-specific geometry`; } else if (stale) { coachLine = "PIVOT stale \u2014 platform changed; re-measure pickups before trusting bar/wing order"; } let completionBenefit = null; if (isLmModPivotClass(ct) && ratio >= 0.85 && !stale) { completionBenefit = "LM/Mod geometry anchored \u2014 shock mounts and birdcage outers enable repeatable RC comparisons."; } else if (ratio >= 0.85 && !stale) { completionBenefit = "Bar/wing suggestions are now anchored to your car's measured pickups."; } else if (ratio >= 0.55 && missingShocks.length === 0 && !stale) { completionBenefit = "RC comparisons are becoming repeatable \u2014 finish remaining inners for full anchor."; } else if (measured.length > 0 && ratio < 0.55) { completionBenefit = "Each pickup you log replaces generic builder hole charts with your car's geometry."; } return { tier: PIVOT_TIER.FULL, expected: expectedCount, measured: measured.length, measuredKeys: measured, missing, missingLabels, missingShocks, optionalMissing, ratio, stale, complete: ratio >= 0.85 && missingShocks.length === 0 && !stale, fresh: !stale, trustLevel, headline, coachLine, completionBenefit, platformIntegrity: pi, tierInfo, mode: "full_xyz" }; } function resolveGeometryTrustMode(vc2 = {}, profile2 = "") { const tierInfo = resolvePivotTier(vc2, profile2); const features = tierInfo.features || pivotTierFeatures(tierInfo.tier); if (tierInfo.tier === PIVOT_TIER.MINIMAL) { return { mode: "minimal", tier: PIVOT_TIER.MINIMAL, tierInfo, trustLevel: "reference_only", builderWeight: "class_and_builder", pivot: assessPivotCompleteness(vc2, vc2.car_type, profile2), modified: false, majorMod: false, birdcageReindexed: false, referenceOnly: true, carAnchored: false, geometryAwareness: false }; } const pivot = assessPivotCompleteness(vc2, vc2.car_type, profile2); const pi = pivot.platformIntegrity; let builderWeight = "full"; if (tierInfo.tier === PIVOT_TIER.FOCUSED) { if (pivot.stale || pivot.trustLevel === "reference_only") builderWeight = "class_and_builder"; else if (pivot.trustLevel === "refs_complete") builderWeight = "blended"; else builderWeight = "class_and_builder"; return { mode: "focused", tier: PIVOT_TIER.FOCUSED, tierInfo, trustLevel: pivot.trustLevel, builderWeight, pivot, modified: pi.delivery_status && pi.delivery_status !== DELIVERY_STATUS.STOCK, majorMod: pi.delivery_status === DELIVERY_STATUS.SIGNIFICANT || pi.delivery_status === DELIVERY_STATUS.MAJOR || pi.modifications.some((m) => STALE_TRIGGER_MODS.has(m)), birdcageReindexed: pi.birdcage_reindexed, referenceOnly: pivot.stale || pivot.trustLevel === "reference_only", carAnchored: false, geometryAwareness: pivot.trustLevel === "refs_complete" || pivot.ratio >= 0.75 }; } if (pivot.stale) builderWeight = "reference_only"; else if (pivot.trustLevel === "reference_only") builderWeight = "class_and_builder"; else if (pivot.trustLevel === "builder_reference") builderWeight = "blended"; else builderWeight = "car_first"; const modified = pi.delivery_status && pi.delivery_status !== DELIVERY_STATUS.STOCK; const majorMod = pi.delivery_status === DELIVERY_STATUS.SIGNIFICANT || pi.delivery_status === DELIVERY_STATUS.MAJOR || pi.modifications.some((m) => STALE_TRIGGER_MODS.has(m)); return { mode: isAdvancedChassisProfile(profile2) || isLateModelEvolutionProfile(profile2) ? "advanced" : "full", tier: PIVOT_TIER.FULL, tierInfo, trustLevel: pivot.trustLevel, builderWeight, pivot, modified, majorMod, birdcageReindexed: pi.birdcage_reindexed, referenceOnly: pivot.stale || pivot.trustLevel === "reference_only", carAnchored: pivot.trustLevel === "car_anchored" && !pivot.stale, geometryAwareness: pivot.trustLevel === "car_anchored" }; } function buildPlatformIntegritySave(car = {}, payload = {}) { const prev = normalizePlatformIntegrity(car.platform_integrity || car.geometry_state?.platform_integrity); const next = normalizePlatformIntegrity({ delivery_status: payload.delivery_status ?? prev.delivery_status, modifications: payload.modifications ?? prev.modifications, birdcage_reindexed: payload.birdcage_reindexed ?? prev.birdcage_reindexed, multi_shock_rear: payload.multi_shock_rear ?? prev.multi_shock_rear, compact_trailing_arm: payload.compact_trailing_arm ?? prev.compact_trailing_arm, other_notes: payload.other_notes ?? prev.other_notes, updated_at: (/* @__PURE__ */ new Date()).toISOString() }); const shouldStale = modificationsShouldStalePickups(next.modifications, next.delivery_status); let geometry_state = car.geometry_state ? { ...car.geometry_state } : { version: 1 }; const tierInfo = resolvePivotTier(car); const majorOnlyStale = tierInfo.tier === PIVOT_TIER.FOCUSED; const staleApplies = shouldStale && (!majorOnlyStale || next.delivery_status === DELIVERY_STATUS.MAJOR || next.delivery_status === DELIVERY_STATUS.SIGNIFICANT || next.modifications.includes("coilover_conversion")); if (staleApplies && countMeasuredPickups(geometry_state.pickup_points) > 0) { const reasons = []; if (next.delivery_status === DELIVERY_STATUS.MAJOR) reasons.push("major conversion"); if (next.delivery_status === DELIVERY_STATUS.SIGNIFICANT) reasons.push("significant changes"); next.modifications.filter((m) => STALE_TRIGGER_MODS.has(m)).forEach((m) => { const opt = MODIFICATION_OPTIONS.find((o) => o.id === m); if (opt) reasons.push(opt.label.toLowerCase()); }); geometry_state = markGeometryPickupsStale( geometry_state, `Platform changed (${reasons.slice(0, 2).join(", ")}) \u2014 re-measure pickups` ); next.pickups_stale = true; next.stale_reason = geometry_state.pickup_points.stale_reason; next.stale_since = geometry_state.pickup_points.stale_since; } else if (staleApplies && tierInfo.tier === PIVOT_TIER.FOCUSED) { next.pickups_stale = true; next.stale_reason = "Platform changed \u2014 re-log J-bar/panhard/rear refs"; next.stale_since = (/* @__PURE__ */ new Date()).toISOString(); } else if (!shouldStale && prev.pickups_stale && next.delivery_status === DELIVERY_STATUS.STOCK && !next.modifications.length) { next.pickups_stale = false; next.stale_reason = null; next.stale_since = null; } else { next.pickups_stale = prev.pickups_stale || geometry_state.pickup_points?.status === "stale"; next.stale_reason = prev.stale_reason || geometry_state.pickup_points?.stale_reason || null; next.stale_since = prev.stale_since || geometry_state.pickup_points?.stale_since || null; } geometry_state.platform_integrity = next; return { platform_integrity: next, geometry_state }; } function evaluatePivotFreshness(car = {}) { const vc2 = { geometry_state: car.geometry_state, platform_integrity: car.platform_integrity || car.geometry_state?.platform_integrity, front_susp: car.front_susp, car_type: car.car_type }; const pivot = assessPivotCompleteness(vc2, car.car_type); const pi = normalizePlatformIntegrity(vc2.platform_integrity); let geometry_state = car.geometry_state ? { ...car.geometry_state } : { version: 1 }; if (pivot.complete && pi.pickups_stale) { geometry_state = clearPickupStaleFlag(geometry_state); pi.pickups_stale = false; pi.stale_reason = null; pi.stale_since = null; if (geometry_state.pickup_points) { geometry_state.pickup_points.status = `${car.car_type || "car"}_complete`; } } pi.updated_at = pi.updated_at || (/* @__PURE__ */ new Date()).toISOString(); geometry_state.platform_integrity = pi; return { platform_integrity: pi, geometry_state, pivot, benefit: pivot.completionBenefit }; } // scripts/lib/experimental/advancedChassisPrioritization.mjs var ADVANCED_ACTION_REC_CAP = 5; var RACE_NIGHT_ACTION_CAP = 5; var PHASE_SYMPTOM = { entry_push: { label: "ENTRY PUSH", phase: "entry" }, entry_loose: { label: "ENTRY LOOSE", phase: "entry" }, mid_push: { label: "MID PUSH", phase: "mid" }, exit_loose: { label: "EXIT LOOSE", phase: "exit" }, exit_tight: { label: "EXIT BIND", phase: "exit" }, tight_in_loose_off: { label: "TIGHT IN / LOOSE OFF", phase: "split" }, inconsistent: { label: "INCONSISTENT", phase: "repeatability" } }; function detectAdvancedFeelSymptom(vc2 = {}) { const entry = String(vc2.trait_entry || "").toLowerCase(); const mid = String(vc2.trait_mid || "").toLowerCase(); const exit = String(vc2.trait_exit || "").toLowerCase(); const hay = `${entry} ${mid} ${exit}`; if (/tight.*loose|tight in.*loose off|tight off|push.*center.*loose/.test(hay)) { return { key: "tight_in_loose_off", ...PHASE_SYMPTOM.tight_in_loose_off }; } if (/inconsistent|on.?off|peaky|never same/.test(hay)) { return { key: "inconsistent", ...PHASE_SYMPTOM.inconsistent }; } if (/push|tight|understeer|wont turn|wash/.test(entry) && !/push|tight/.test(exit)) { return { key: "entry_push", ...PHASE_SYMPTOM.entry_push }; } if (/loose|free|oversteer|rotate/.test(entry) && !/loose|free/.test(mid + exit)) { return { key: "entry_loose", ...PHASE_SYMPTOM.entry_loose }; } if (/push|flat|tight|bind/.test(mid)) { return { key: "mid_push", ...PHASE_SYMPTOM.mid_push }; } if (/loose|free|spin|freed/.test(exit)) { return { key: "exit_loose", ...PHASE_SYMPTOM.exit_loose }; } if (/tight|bind|hook|won't drive|no drive/.test(exit)) { return { key: "exit_tight", ...PHASE_SYMPTOM.exit_tight }; } return null; } function advancedTryFirstFromSymptom(symptom, trackState, opts = {}) { if (!symptom) return null; const track = String(trackState || "").toLowerCase(); const slick = /slick|drying|heavy/.test(track); const strongCtx = (opts.completeness ?? 0) >= 55 && opts.hasScale; const rules = { entry_push: { action: slick ? "Wing down 0.5\xB0 OR J-bar/panhard up one hole \u2014 entry push on slick is usually excess wing load on a low RC platform" : "Wing down 0.5\xB0 OR raise J-bar/panhard one hole \u2014 entry push with high wing means RC cannot support load", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Entry push \u2192 reduce wing load OR raise rear RC before front hero moves") }, entry_loose: { action: "J-bar/panhard down one hole OR wing up 0.5\xB0 \u2014 entry loose needs RR load or aero help; pick one lever this run", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Entry loose \u2192 RC bite or wing, not both same run") }, mid_push: { action: slick ? "If the car rolls then pushes: wing down 0.5\xB0 OR bar up one hole. If flat with no roll: RF platform or cross before rear tighten" : "If the car rolls then pushes: wing down 0.5\xB0 OR bar up one hole. If flat with no roll: RF platform or cross before rear tighten", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Mid push separates roll/wing overload from platform issues") }, exit_loose: { action: "LR bite first \u2014 bar down one hole, stagger, or RR platform BEFORE wing add \xB7 exit loose rarely fixed by more wing when RC roll is already high", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Exit phase \u2192 RC/stagger before aero") }, exit_tight: { action: "Bar up one hole OR wing down 0.5\xB0 \u2014 exit bind is usually too much roll or wing load into RR \xB7 log hot RR psi lap 8\u201310", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Exit bind \u2192 unload RC or wing before shock chase") }, tight_in_loose_off: { action: "Fix entry ONLY this run \u2014 raise bar or wing down for entry push \xB7 save exit (bar down/stagger) for next A-B-A \xB7 split-phase beats stacked bite", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Two-phase problem \u2192 one phase per run") }, inconsistent: { action: "Re-scale and log bar + wing position \u2014 no setup change until lap-to-lap repeatability returns \xB7 inconsistent feel is platform drift, not missing bite", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Repeatability before speed at this level") } }; const rule = rules[symptom.key]; if (!rule) return null; const confidence2 = strongCtx ? "MODERATE" : "LOW"; const caveat = strongCtx ? "Logged feel + scale on file \u2014 run one lever, then A\u2032 on the same track state." : "Phase-specific move from logged feel \u2014 log scale/bar/wing row to sharpen magnitude."; return { action: rule.action, lever: `advanced_tryfirst_${symptom.key}`, basis: "prior", confidence: confidence2, refs: ["advanced_prioritization"], reason: rule.reason, caveat, flags: { try_first: true, advanced_chassis: true, advanced_geometry: true, advanced_try_first: true, symptom_key: symptom.key, symptom_label: symptom.label, profile: "advanced" } }; } function applyAdvancedPrioritization(recs, ctx = {}, recWeight2) { const profile2 = ctx.profile; const isRaceNightProfile = isAdvancedChassisProfile(profile2) || isLateModelEvolutionProfile(profile2); if (!isRaceNightProfile) { return { tryFirst: null, tryFirstReason: null, canWait: [], prioritizationNote: null, actionRecommendations: recs.slice(0, ADVANCED_ACTION_REC_CAP), processRecommendations: [], diagnosedSymptom: null }; } const vc2 = ctx.vehicleContext || {}; const geometryTrust = resolveGeometryTrustMode(vc2, profile2); const symptom = detectAdvancedFeelSymptom(vc2); const completeness = ctx.contextAssessment?.completeness ?? 0; const m = vc2.setup_measurements || {}; const hasScale = m.lf_psi != null && m.rf_psi != null || m.stagger != null || m.lr_bar_turns != null; const sessionLog = ctx.sessionLog || vc2.session_log || []; let tryRec = recs.find((r) => r.basis === "finding") || null; let tryReason = tryRec ? tryRec.confidence === "HIGH" ? "Your logged finding survived correction \u2014 book this move, then A\u2032 before stacking another lever." : "Logged finding on your car \u2014 run A-B-A on the same track state before adding bite elsewhere." : null; if (!tryRec) { tryRec = recs.find((r) => r.flags?.builder_signal && !r.flags?.scaffold) || null; if (tryRec) { tryReason = `${tryRec.flags?.builder_mfr || "Builder"} signal matches your logged bar/wing platform \u2014 validate with A\u2032 tonight.`; } } if (!tryRec) { tryRec = recs.find((r) => r.flags?.track_evolution_conflict) || null; if (tryRec) tryReason = "Track snap and session log disagree on direction \u2014 reconcile before booking a setup move."; } if (!tryRec && symptom) { tryRec = advancedTryFirstFromSymptom(symptom, ctx.trackState, { completeness, hasScale }); if (tryRec) { tryRec = enrichAdvancedTryFirstWithLoggedData(tryRec, vc2); tryReason = strongTryFirstReason(symptom, completeness, hasScale); } } if (!tryRec) { tryRec = recs.find((r) => r.flags?.advanced_between_session && r.lever === "advanced_between_one_lever") || recs.find((r) => r.flags?.late_model_evolution && r.flags?.advanced_forward_plan) || recs.find((r) => r.lever === "advanced_log_minimum") || recs.find((r) => r.lever === "advanced_geo_bar_vs_wing_first") || recs.find((r) => r.flags?.advanced_geometry) || recs.find((r) => r.lever === "advanced_data_priority") || recs.find((r) => r.flags?.aba_discipline && r.lever === "aba_protocol_clean") || null; if (tryRec?.lever === "advanced_log_minimum") { tryReason = "Thin log \u2014 one scale + bar/wing + phase feel row unlocks car-specific guidance for the rest of the night."; } else if (tryRec?.flags?.advanced_forward_plan || tryRec?.flags?.late_model_evolution) { tryReason = "Track is evolving \u2014 pre-pick one lever (bar OR wing OR stagger) before the next session."; } else if (tryRec) { tryReason = "Platform discipline before fine tuning \u2014 geometry and logging beat shock chasing when data is thin."; } } if (tryRec && !tryRec.flags?.try_first) { tryRec = { ...tryRec, flags: { ...tryRec.flags, try_first: true } }; } const tierBoost = (r) => { let b = 0; if (r.basis === "finding") b += 0.2; if (r.flags?.builder_signal) b += 0.07; if (r.flags?.aba_discipline && r.lever?.startsWith("aba_interpret")) b += 0.06; if (r.flags?.aba_discipline) b += 0.03; if (r.flags?.advanced_between_session) b += 0.05; if (r.flags?.late_model_evolution && !r.flags?.scaffold) b += 0.045; if (r.flags?.advanced_track_evolution && r.flags?.advanced_forward_plan) b += 0.04; if (r.flags?.track_evolution_conflict) b += 0.05; if (r.lever === "advanced_log_minimum" && completeness < 50) b += 0.15; if (r.flags?.advanced_geometry && /bar_vs_wing|wing_rc|tryfirst/.test(r.lever || "")) b += 0.08; if (r.flags?.advanced_logging) b += 0.05; if (r.flags?.scaffold) b -= 0.08; if (r.flags?.public_baseline && !r.flags?.chassis_specific) b -= 0.04; if (r.flags?.lm_mod_modern) b += 0.05; if (geometryTrust.referenceOnly && (r.flags?.advanced_geometry || r.flags?.builder_signal)) b -= 0.1; if (geometryTrust.carAnchored && r.flags?.advanced_geometry) b += 0.06; if (geometryTrust.geometryAwareness && r.flags?.builder_signal && !r.flags?.scaffold) b += 0.04; return b; }; const ranked = [...recs].sort((a, b) => recWeight2(b) + tierBoost(b) - (recWeight2(a) + tierBoost(a))); if (tryRec) { const idx = ranked.findIndex((r) => r.lever === tryRec.lever && r.action === tryRec.action); if (idx > 0) { ranked.splice(idx, 1); ranked.unshift(tryRec); } else if (idx < 0) { ranked.unshift(tryRec); } } ranked.forEach((r, i) => { r.rank = i + 1; }); if (geometryTrust.referenceOnly) { ranked.forEach((r) => { if ((r.flags?.advanced_geometry || r.flags?.builder_signal) && !r.flags?.try_first && r.basis !== "finding") { r.flags = { ...r.flags, pivot_downgraded: true, pivot_reference_only: true }; } }); } const actionRecommendations = ranked.slice(0, RACE_NIGHT_ACTION_CAP); const processRecommendations = ranked.filter( (r) => r.flags?.advanced_logging || r.flags?.aba_discipline || r.lever === "advanced_log_session" ); const canWait = ranked.filter((r) => (r.flags?.scaffold || r.flags?.public_baseline) && !r.flags?.try_first && r.basis !== "finding").slice(0, 3).map((r) => ({ label: r.lever.replace(/^(advanced_|aba_|builder_)/, ""), action: r.action })); let prioritizationNote = null; if (tryRec?.basis === "finding") { prioritizationNote = `Book tonight: ${stripActionPrefix(tryRec.action).slice(0, 100)}`; } else if (symptom && tryRec?.flags?.advanced_try_first) { prioritizationNote = `${symptom.label} \u2192 ${stripActionPrefix(tryRec.action).slice(0, 90)}`; } else if (tryRec?.flags?.builder_signal) { prioritizationNote = `${tryRec.flags?.builder_mfr || "Builder"} signal \u2192 ${stripActionPrefix(tryRec.action).slice(0, 90)}`; } else if (geometryTrust.tier === PIVOT_TIER.FOCUSED && geometryTrust.geometryAwareness) { prioritizationNote = "Focused PIVOT on file \u2014 builder priors can reference your logged J-bar/panhard/rear refs."; } else if (geometryTrust.pivot?.stale) { prioritizationNote = "Platform changed \u2014 re-measure PIVOT pickups; tonight use builder/class reference until geometry is fresh."; } else if (geometryTrust.referenceOnly && geometryTrust.pivot?.measured < 3) { prioritizationNote = "Thin PIVOT \u2014 reference-only geometry tonight; finish pickup wizard to unlock car-specific lever order."; } else if (completeness < 40) { prioritizationNote = "Tonight: log scale + bar/wing + phase feel \u2014 reference notes stay generic until your row exists."; } else if (tryRec) { prioritizationNote = `Focus: ${stripActionPrefix(tryRec.action).slice(0, 100)}`; } const dataQualityNote = buildAdvancedDataQualityNote(ctx.contextAssessment, sessionLog, { trackState: ctx.trackState, pivotStatus: geometryTrust.pivot, geometryTrust }); const tryFirstTier = tryRec ? formatRecommendationTier(tryRec) : null; const bookNow = []; const validate = []; const reference = []; for (const r of ranked) { const tier = formatRecommendationTier(r); if (tier.id === "book" || tier.id === "validate") { if (tier.id === "book") bookNow.push(r); else validate.push(r); } else if (tier.id === "reference") reference.push(r); } return { tryFirst: tryRec, tryFirstReason: tryReason, tryFirstTier, canWait, prioritizationNote, dataQualityNote, actionRecommendations, processRecommendations, diagnosedSymptom: symptom, recommendationGroups: { bookNow: bookNow.slice(0, 3), validate: validate.slice(0, 4), reference: reference.slice(0, 4), canWait }, geometryTrust, pivotStatus: geometryTrust.pivot }; } function stripActionPrefix(action = "") { return String(action).replace(/^Try first:\s*/i, "").trim(); } function strongTryFirstReason(symptom, completeness, hasScale) { if (completeness >= 55 && hasScale) { return `${symptom.label} with scale on file \u2014 bar vs wing order is set; run ONE lever, then A\u2032.`; } if (hasScale) { return `${symptom.label} logged \u2014 phase-specific lever order below; add feel rows to sharpen magnitude.`; } return `${symptom.label} \u2014 log scale + bar/wing before the run so the move is measurable.`; } function advancedTryFirstRecTag(r) { if (r.flags?.symptom_label) return `[TRY FIRST \xB7 ${r.flags.symptom_label}]`; if (r.flags?.advanced_logging) return "[TRY FIRST \xB7 LOG]"; if (r.flags?.advanced_geometry) return "[TRY FIRST \xB7 GEOMETRY]"; return "[TRY FIRST \xB7 ADV]"; } // scripts/lib/experimental/advancedRaceNightDecisions.mjs var BETWEEN_SESSION_LEAVE_ALONE = { lever: "advanced_between_leave_platform", action: "Leave platform alone this break: pickup points + bar hole + wing position are your baseline \u2014 if last run was within 0.2s of your best, log hot psi and feel only; no geometry move until A-B-A is planned", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Platform stability beats hero changes between back-to-back sessions") }; var BETWEEN_SESSION_RH_DRIFT = { lever: "advanced_between_rh_drift", action: "AFRH drift vs locked baseline: fix ride height FIRST (corner that moved) before bar or wing \u2014 RH change redefines RC; bar/wing moves on wrong height invalidate the log", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "As-found RH drift is a platform correction, not a handling tune") }; var BETWEEN_SESSION_ONE_LEVER = { lever: "advanced_between_one_lever", action: "Next session: ONE lever only \u2014 bar height OR bar angle OR wing step OR stagger \u2014 write the hypothesis on the scale sheet before rolling out", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Stacked between-session changes erase cause/effect in the log") }; var BETWEEN_SESSION_GEOMETRY_INCOMPLETE = { lever: "advanced_between_geometry_gap", action: "PIVOT pickup points incomplete \u2014 finish inner A-arm and rear pickup references before chasing shock or tire hero moves; geometry_state is how you compare nights apples-to-apples", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Incomplete pickup log blocks repeatable RC comparison") }; var BETWEEN_SESSION_FEEL_ONLY = { lever: "advanced_between_feel_logged", action: "Feel logged without scale row \u2014 next break: re-scale + log bar/wing position, then apply ONE phase-specific fix from try-first (entry vs exit are different problems)", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Traits enable phase diagnosis; scale row validates the move") }; function countPickupPoints(enrichedVc) { const gs = enrichedVc.geometry_state; const pp = gs?.pickup_points; if (!pp || typeof pp !== "object") return 0; return Object.keys(pp).filter((k) => { const v = pp[k]; return v && typeof v === "object" && v.x != null; }).length; } function hasFourCornerRh(enrichedVc) { const m = enrichedVc.setup_measurements || {}; const afrh = enrichedVc.as_found_rh; if (afrh?.lf != null && afrh.rf != null && afrh.lr != null && afrh.rr != null) return true; return m.ride_ht_lf != null && m.ride_ht_rf != null && m.ride_ht_lr != null && m.ride_ht_rr != null; } function advancedBetweenSessionPriors(enrichedVc = {}, trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const max = opts.maxPriors ?? 3; const priors = []; const pickupCount = enrichedVc.pickup_points_count ?? countPickupPoints(enrichedVc); const pickupComplete = enrichedVc.pickup_points_complete || /complete/i.test(String(enrichedVc.geometry_state?.pickup_points?.status || "")); const symptom = detectAdvancedFeelSymptom(enrichedVc); const m = enrichedVc.setup_measurements || {}; const hasScale = m.lf_psi != null && m.rf_psi != null || m.stagger != null || m.left_pct != null && m.rear_pct != null; const hasBar = m.lr_bar_turns != null || m.panhard != null || m.j_bar_height != null; const rhDrift = enrichedVc.rh_drift_at_start || enrichedVc.as_found_rh && enrichedVc.locked_setup_ref; if (!pickupComplete && pickupCount < 4) { priors.push(BETWEEN_SESSION_GEOMETRY_INCOMPLETE); } if (rhDrift && hasFourCornerRh(enrichedVc)) { priors.push(BETWEEN_SESSION_RH_DRIFT); } if (symptom && !hasScale) { priors.push(BETWEEN_SESSION_FEEL_ONLY); } if (symptom && hasScale && hasBar && pickupCount >= 4) { priors.push(BETWEEN_SESSION_ONE_LEVER); } if (hasScale && hasBar && !symptom) { priors.push(BETWEEN_SESSION_LEAVE_ALONE); } const track = String(trackState || "").toLowerCase(); if (/slick|drying/.test(track) && priors.length < max) { priors.push({ lever: "advanced_between_slick_evolution", action: "Track slicking \u2014 between sessions: note line move + hot RR psi trend before adding bite; slick tracks punish stacked bar + wing on a platform that already rolled too much", reason: tagReason(ADVANCED_SOURCE.PRINCIPLE, "Surface evolution changes the correct lever order") }); } return priors.filter((p) => !skip.has(p.lever)).slice(0, max); } function pickAdvancedGeometryStateLine(enrichedVc = {}) { const n = enrichedVc.pickup_points_count ?? countPickupPoints(enrichedVc); if (n <= 0) return null; const complete = enrichedVc.pickup_points_complete || /complete/i.test(String(enrichedVc.geometry_state?.pickup_points?.status || "")); if (complete) return `PIVOT: ${n} pickup refs on file \u2014 RC comparisons are repeatable`; return `PIVOT: ${n} pickup refs logged \u2014 finish inner front + rear pickups for full baseline`; } function pickAdvancedShockVaultLine(enrichedVc = {}) { const vault = enrichedVc.shock_vault; if (!Array.isArray(vault) || !vault.length) return null; const pos = vault.filter((s) => s.position).length; return pos ? `Shock vault: ${vault.length} on file (${pos} positioned)` : `Shock vault: ${vault.length} shocks logged`; } function enrichAdvancedTryFirstWithLoggedData(tryRec, enrichedVc = {}) { if (!tryRec?.flags?.advanced_try_first) return tryRec; const m = enrichedVc.setup_measurements || {}; const bits = []; if (m.lr_bar_turns != null) bits.push(`LR bar ${m.lr_bar_turns} turns`); if (m.wing_angle != null) bits.push(`wing ${m.wing_angle}\xB0`); if (enrichedVc.trait_entry || enrichedVc.trait_mid || enrichedVc.trait_exit) { bits.push(`feel ${[enrichedVc.trait_entry, enrichedVc.trait_mid, enrichedVc.trait_exit].filter(Boolean).join("/")}`); } if (!bits.length) return tryRec; return { ...tryRec, action: `${tryRec.action} \xB7 On file: ${bits.join(" \xB7 ")}` }; } // scripts/lib/experimental/advancedChassisContextPersonalization.mjs var REF2 = { maxim_sprint_winged: { left_pct: { low: 52, high: 58, label: "sprint left-side ~52\u201358%" }, stagger: { low: 3, high: 10, label: "sprint rear stagger (track-size dependent)" } }, hyper_midget: { left_pct: { low: 54, high: 60, label: "midget left-side ~54\u201360%" }, stagger: { low: 4, high: 8, label: "midget rear stagger ~4\u20138 in" } } }; function num4(v) { const n = Number(v); return Number.isFinite(n) ? n : null; } function compareToBand2(value, band) { if (value == null || !band) return null; if (value < band.low) return `below ${band.label}`; if (value > band.high) return `above ${band.label}`; return `within ${band.label}`; } function assessAdvancedContext(enrichedVc = {}, trackState, profile2) { const m = enrichedVc.setup_measurements || {}; const measCount = Object.keys(m).filter((k) => m[k] != null && m[k] !== "").length; const hasChassis = enrichedVc.chassis_routing_confidence === "identified" && Boolean(enrichedVc.chassis_mfr_key); const hasDeepBuilder = isAdvancedDeepBuilder(enrichedVc.chassis_mfr_key); const hasTrack = Boolean(trackState && String(trackState).toLowerCase() !== "unknown"); const hasFeel = Boolean( enrichedVc.trait_entry || enrichedVc.trait_mid || enrichedVc.trait_exit ); const hasBarTurns = m.lr_bar_turns != null || m.rr_bar_turns != null; const pickupCount = enrichedVc.pickup_points_count ?? 0; const hasGeometryState = pickupCount >= 4 || enrichedVc.pickup_points_complete || /complete/i.test(String(enrichedVc.geometry_state?.pickup_points?.status || "")); const hasAfrh = Boolean( enrichedVc.as_found_rh || m.ride_ht_lf != null && m.ride_ht_rf != null && m.ride_ht_lr != null && m.ride_ht_rr != null ); const hasShockVault = Array.isArray(enrichedVc.shock_vault) && enrichedVc.shock_vault.length > 0; const hasCoreMeas = measCount >= 2 || m.rf_psi != null && m.lf_psi != null || m.stagger != null || m.left_pct != null && m.rear_pct != null || hasBarTurns; let score = 0; if (hasChassis) score += 30; if (hasDeepBuilder) score += 10; if (hasCoreMeas) score += 30; if (hasTrack) score += 20; if (hasFeel) score += 10; if (hasGeometryState) score += 10; if (hasAfrh) score += 5; if (hasShockVault) score += 5; return { profile: profile2, layer: "advanced_chassis", hasChassis, hasDeepBuilder, hasCoreMeas, hasTrack, hasFeel, hasBarTurns, hasGeometryState, hasAfrh, hasShockVault, pickupCount, measCount, completeness: Math.min(100, score), chassisLabel: enrichedVc.chassis_manufacturer || enrichedVc.chassis_name || null, builderKey: enrichedVc.chassis_mfr_key || null, caveat: ADVANCED_CHASSIS_CAVEAT }; } function measurementInsightPriors2(profile2, enrichedVc) { const bands = REF2[profile2]; const m = enrichedVc.setup_measurements || {}; if (!bands) return []; const priors = []; const checks = [ ["left_pct", num4(m.left_pct), bands.left_pct, "left-side %"], ["stagger", num4(m.stagger), bands.stagger, "rear stagger"] ]; for (const [, value, band, label] of checks) { const cmp = compareToBand2(value, band); if (!cmp) continue; priors.push({ lever: `advanced_ctx_${label.replace(/\s+/g, "_")}`, action: `Your ${label} is ${cmp} \u2014 log A-B-A before adopting another builder's baseline numbers`, reason: "[YOUR DATA] Compare against your history, not cross-brand sheets" }); } if (!m.lr_bar_turns && !m.rr_bar_turns && isAdvancedDeepBuilder(enrichedVc.chassis_mfr_key)) { priors.push({ lever: "advanced_ctx_bar_turns", action: "Add LR/RR bar stop turns to your scale sheet \u2014 at this level, turns are the repeatable reference between nights", reason: "[DATA POLICY] Bar turns missing from logged setup \u2014 add before comparing sessions" }); } return priors; } function contextGapPrior2(assessment) { if (!assessment) return null; if (!assessment.hasChassis) { return { lever: "advanced_ctx_gap_chassis", action: "Add chassis builder (Mach 1, J&J, KRK, Fletcher, etc.) in Garage \u2192 Setup \u2192 Chassis", reason: "[REFERENCE] Builder routing unlocks builder-specific reference notes" }; } if (!assessment.hasCoreMeas) { return { lever: "advanced_ctx_gap_scale", action: "Log scale basics: tire psi, stagger, left/rear %, LR/RR bar turns when available", reason: "[DATA POLICY] Logged scale data is the primary accuracy driver in these classes" }; } if (!assessment.hasTrack) { return { lever: "advanced_ctx_gap_track", action: "Set track state (tacky / drying / slick) \u2014 geometry and wing plans depend on evolution", reason: "[REFERENCE] Track state drives stagger/bar/wing sequencing on sprint cars" }; } if (!assessment.hasFeel) { return { lever: "advanced_ctx_gap_feel", action: "Optional: log entry/mid/exit feel in Setup \u2014 enables phase-first lever choice when data is thin", reason: "[REFERENCE] Directional feel improves geometry vs bar vs wing decisions" }; } if (!assessment.hasGeometryState && assessment.hasDeepBuilder) { return { lever: "advanced_ctx_gap_geometry_state", action: "Run PIVOT pickup points (inner A-arms + rear pickups) \u2014 geometry_state is how builder-level RC comparisons stay repeatable between nights", reason: "[DATA POLICY] Logged pickup refs unlock car-specific geometry guidance" }; } return null; } function buildAdvancedContextBlock(vc2 = {}, trackState, profile2, skipLevers = /* @__PURE__ */ new Set()) { if (!isAdvancedChassisProfile(profile2)) { return { priors: [], gapPriors: [], assessment: null, enrichedVc: vc2 }; } const enriched = enrichAdvancedVehicleContext(vc2, profile2); const assessment = assessAdvancedContext(enriched, trackState, profile2); const priors = measurementInsightPriors2(profile2, enriched).filter((p) => !skipLevers.has(p.lever)); const gap = contextGapPrior2(assessment); const gapPriors = gap && !skipLevers.has(gap.lever) ? [gap] : []; return { priors, gapPriors, assessment, enrichedVc: enriched }; } function buildAdvancedCarSummaryLine(enrichedVc, assessment) { if (!assessment || assessment.completeness < 20) return null; const parts = []; if (assessment.chassisLabel) parts.push(assessment.chassisLabel); const m = enrichedVc.setup_measurements || {}; if (m.rf_psi != null || m.lf_psi != null) { parts.push(`psi LF ${m.lf_psi ?? "?"}/RF ${m.rf_psi ?? "?"}`); } if (m.stagger != null) parts.push(`stagger ${m.stagger}"`); if (m.lr_bar_turns != null || m.rr_bar_turns != null) { parts.push(`bars LR ${m.lr_bar_turns ?? "?"}/RR ${m.rr_bar_turns ?? "?"}`); } if (m.left_pct != null && m.rear_pct != null) { parts.push(`cross ~${(num4(m.left_pct) + num4(m.rear_pct) - 100).toFixed(1)}%`); } if (!parts.length) return null; return `On file: ${parts.join(" \xB7 ")}`; } function buildAdvancedContextGapLine(assessment) { const brief = advancedDriverBriefContextLine(assessment); if (brief) return brief; if (!assessment || assessment.completeness >= 85) return null; if (!assessment.hasChassis) { return "Add sprint/midget chassis builder in Garage \u2192 Setup \u2192 Chassis to unlock builder reference."; } if (!assessment.hasCoreMeas) { return "Log scale sheet (psi, stagger, left/rear %, bar turns) \u2014 your log leads at this level."; } if (!assessment.hasTrack) { return "Set track state (tacky / slick / drying) \u2014 wing and bar plans depend on evolution."; } if (!assessment.hasFeel) { return "Optional: log entry/mid/exit feel for phase-first geometry and bar decisions."; } return null; } function buildAdvancedChassisCoachLine(vc2 = {}, profile2 = "", trackState) { return resolveAdvancedChassisHint(vc2, profile2, trackState); } function buildAdvancedContextCoachPayload(vc2 = {}, trackState, profile2) { const block = buildAdvancedContextBlock(vc2, trackState, profile2); const chips = []; const gaps = []; const enriched = block.enrichedVc || vc2; if (enriched.chassis_name && String(enriched.chassis_name).trim()) { chips.push(`Chassis: ${enriched.chassis_name}`); } else if (enriched.chassis_manufacturer) { chips.push(`Builder: ${enriched.chassis_manufacturer}`); } else { gaps.push("chassis builder"); } const hint = buildAdvancedChassisCoachLine(vc2, profile2, trackState); if (hint) chips.push(hint.split("\xB7")[0].trim()); const geoLine = pickAdvancedGeometryBriefLine(profile2); if (geoLine) chips.push(geoLine); const geoStateLine = pickAdvancedGeometryStateLine(enriched); if (geoStateLine) chips.push(geoStateLine); const shockLine = pickAdvancedShockVaultLine(enriched); if (shockLine) chips.push(shockLine); const logLine = pickAdvancedLoggingBriefLine(); if (logLine) chips.push(logLine); const evoLine = pickAdvancedTrackEvolutionBriefLine( { session_log: vc2.session_log, line_progression: vc2.line_progression, drying_prediction: vc2.drying_prediction }, trackState, profile2 ); if (evoLine) chips.push(evoLine); const m = enriched.setup_measurements || vc2.setup_measurements || {}; const mp = []; if (m.lf_psi != null || m.rf_psi != null) { mp.push(`psi LF ${m.lf_psi ?? "?"}/RF ${m.rf_psi ?? "?"}`); } if (m.stagger != null) mp.push(`stagger ${m.stagger}"`); if (m.lr_bar_turns != null || m.rr_bar_turns != null) { mp.push(`bars LR ${m.lr_bar_turns ?? "?"}/RR ${m.rr_bar_turns ?? "?"}`); } if (m.left_pct != null && m.rear_pct != null) { mp.push(`cross ~${(num4(m.left_pct) + num4(m.rear_pct) - 100).toFixed(1)}%`); } if (mp.length) chips.push(`Scale: ${mp.join(" \xB7 ")}`); else gaps.push("scale sheet (psi/stagger/bars)"); if (!block.assessment?.hasGeometryState && block.assessment?.hasDeepBuilder) { gaps.push("PIVOT pickup points"); } if (trackState && trackState !== "unknown") chips.push(`Track: ${trackState}`); else gaps.push("track state"); if (enriched.trait_entry || enriched.trait_mid || enriched.trait_exit) { chips.push(`Feel: ${[enriched.trait_entry, enriched.trait_mid, enriched.trait_exit].filter(Boolean).join(" / ")}`); } return { chips, gaps, assessment: block.assessment, caveat: ADVANCED_CHASSIS_CAVEAT, header: ADVANCED_COACH_HEADER }; } // scripts/lib/experimental/builderSignalPriors.mjs var BUILDER_TAG = "[BUILDER SIGNAL]"; function detectBuilderKey(vc2 = {}, profile2 = "") { if (isLateModelEvolutionProfile(profile2)) { const hay2 = [ vc2.chassis_name, vc2.chassis_mfr, vc2.chassis_manufacturer, vc2.car_name ].filter(Boolean).join(" ").toLowerCase(); if (/grt|longhorn|malcuit|crippen|best\.?perform/.test(hay2)) { return { key: "grt", mfr: "GRT", confidence: "identified" }; } if (/harris|rayburn|bms|gerry/.test(hay2)) { return { key: "harris", mfr: "Harris", confidence: "identified" }; } if (/swift|rocket|bare|mastercraft/.test(hay2)) { return { key: "swift", mfr: "Swift", confidence: "identified" }; } return profile2 === "modified_dirt" ? { key: "modified_generic", mfr: "Modified", confidence: "class" } : { key: "latemodel_generic", mfr: "Late Model", confidence: "class" }; } if (isAdvancedChassisProfile(profile2)) { const mfr = detectAdvancedChassisManufacturer(profile2, vc2); return { key: mfr.key, mfr: mfr.mfr, confidence: mfr.confidence }; } const hay = String(vc2.chassis_name || vc2.chassis_mfr || "").toLowerCase(); if (/mach[\s-]?1|mach1/.test(hay)) return { key: "mach1", mfr: "Mach 1", confidence: "identified" }; if (/\bj&j\b|\bjj\b|jesse/.test(hay)) return { key: "jj", mfr: "J&J", confidence: "identified" }; if (/\bkrk\b|keen speed/.test(hay)) return { key: "krk", mfr: "KRK", confidence: "identified" }; if (/fletcher/.test(hay)) return { key: "fletcher", mfr: "Fletcher", confidence: "identified" }; if (/hyper|emmick|kiwi|spike|dmi/.test(hay)) return { key: "hyper", mfr: "Hyper", confidence: "identified" }; if (/stanley|bullrider/.test(hay)) return { key: "stanley", mfr: "Stanley", confidence: "identified" }; return { key: null, mfr: null, confidence: "missing" }; } function setupSnapshot(vc2 = {}) { const m = vc2.setup_measurements || {}; return { hasScale: m.lf_psi != null && m.rf_psi != null || m.stagger != null, hasCross: m.left_pct != null && m.rear_pct != null, hasBar: m.lr_bar_turns != null || m.rr_bar_turns != null || m.panhard != null, hasWing: m.wing_angle != null, cross: m.left_pct != null && m.rear_pct != null ? m.left_pct + m.rear_pct - 100 : null, pickupComplete: vc2.pickup_points_complete || /complete/i.test(String(vc2.geometry_state?.pickup_points?.status || "")), pickupCount: vc2.pickup_points_count ?? 0 }; } function signal(lever, action, reason, mfr, extra = {}) { return { lever, action, reason: `${BUILDER_TAG} ${reason}`, builder_key: extra.key, builder_mfr: mfr, ...extra }; } function mach1Signals(snap, track, mfr) { const priors = []; if (snap.hasBar && !snap.hasScale) { priors.push(signal( "builder_mach1_bar_without_scale", "Mach 1 signal: bar turns logged without tonight's scale row \u2014 community teams reset from builder card when cross walks; log psi + bar stops together", tagReason(ADVANCED_SOURCE.INDUSTRY, "Mach 1 shops treat stop turns + scale as one reference"), mfr, { key: "mach1" } )); } if (snap.hasBar && snap.hasScale) { priors.push(signal( "builder_mach1_bar_reference", "Mach 1 signal: log LR/RR bar STOP turns (not just inches) on the scale sheet \u2014 Mach 1 cards compare nights by turn count + cross, not driver memory", tagReason(ADVANCED_SOURCE.INDUSTRY, "Builder-driver model \u2014 repeatable bar reference"), mfr, { key: "mach1" } )); } if (/slick|drying/.test(track) && snap.hasWing) { priors.push(signal( "builder_mach1_slick_wing", "Mach 1 slick signal: wing down 0.5\xB0 as its own A-B-A before LR bar up \u2014 PA bullring Mach 1 teams protect RR for feature, not lap-1 hero bite", tagReason(ADVANCED_SOURCE.PRINCIPLE, "Mach 1 community slick order: stagger/wing before stacked bar"), mfr, { key: "mach1" } )); } if (!snap.pickupComplete && snap.pickupCount > 0 && snap.pickupCount < 6) { priors.push(signal( "builder_mach1_pickup_gap", "Mach 1 signal: finish PIVOT pickup refs before copying J&J/Maxim birdcage holes \u2014 Mach 1 index differs car-to-car", tagReason(ADVANCED_SOURCE.INDUSTRY, "Builder-specific geometry index"), mfr, { key: "mach1" } )); } return priors; } function jjSignals(snap, track, mfr) { const priors = []; if (snap.hasWing) { priors.push(signal( "builder_jj_wing_position", "J&J signal: log wing angle AND fore/aft position \u2014 J&J bullring setups often move wing location before angle on entry push nights", tagReason(ADVANCED_SOURCE.INDUSTRY, "Community J&J teams \u2014 wing position is a primary knob"), mfr, { key: "jj" } )); } if (/slick|drying/.test(track)) { priors.push(signal( "builder_jj_slick_order", "J&J slick signal: stagger down before LR bar stiffen \xB7 wing step separate run \xB7 heat scale drift drives feature plan on J&J cards", tagReason(ADVANCED_SOURCE.PRINCIPLE, "J&J community slick-night lever order"), mfr, { key: "jj" } )); } if (snap.hasCross && snap.cross != null && snap.cross > 54) { priors.push(signal( "builder_jj_cross_high", "J&J signal: cross above ~54% on file \u2014 J&J teams often find RF platform relief before adding LR bite; log hot RF psi before bar turn", tagReason(ADVANCED_SOURCE.INDUSTRY, "High cross + push often = front platform, not rear hero"), mfr, { key: "jj" } )); } return priors; } function krkSignals(snap, track, mfr) { const priors = []; priors.push(signal( "builder_krk_philosophy", "KRK signal: Keen Speed / KRK lineage \u2014 start dealer card, log bar turns every session \xB7 KRK stagger windows often tighter than Maxim on 1/4-mile bullrings", tagReason(ADVANCED_SOURCE.INDUSTRY, "KRK community \u2014 not a Maxim clone sheet"), mfr, { key: "krk" } )); if (/tacky|heavy/.test(track) && snap.hasScale) { priors.push(signal( "builder_krk_tacky_baseline", "KRK tacky signal: establish card baseline scale in hot laps \u2014 KRK teams note LR platform before wing add even when track has bite", tagReason(ADVANCED_SOURCE.PRINCIPLE, "Tacky night still needs logged reference row"), mfr, { key: "krk" } )); } return priors; } function fletcherSignals(snap, mfr) { return [ signal( "builder_fletcher_card", "Fletcher signal: dealer baseline card for model year is primary \u2014 Fletcher bar bands and birdcage holes differ from J&J/Maxim; one logged change per run", tagReason(ADVANCED_SOURCE.INDUSTRY, "Fletcher community \u2014 card over cross-brand copy"), mfr, { key: "fletcher" } ), snap.hasBar && !snap.hasScale ? signal( "builder_fletcher_scale_gap", "Fletcher signal: bar logged without scale \u2014 Fletcher teams remeasure cross when stagger or tire circumference changes", tagReason(ADVANCED_SOURCE.INDUSTRY, "Scale drift invalidates bar comparison"), mfr, { key: "fletcher" } ) : null ].filter(Boolean); } function grtSignals(snap, track, mfr) { const priors = []; priors.push(signal( "builder_grt_cross", "GRT signal: late model cross in low 50s on standard sheet \u2014 RF wedge before LR weight on push \xB7 log hot RF/LR psi each session", tagReason(ADVANCED_SOURCE.INDUSTRY, "GRT public setup philosophy \u2014 mechanical balance first"), mfr, { key: "grt" } )); if (/slick|drying/.test(track)) { priors.push(signal( "builder_grt_slick_compound", "GRT slick signal: compound step (D40\u2192D30) before cross chase when RR temp spikes \u2014 GRT teams protect tire over hero wedge on long runs", tagReason(ADVANCED_SOURCE.INDUSTRY, "Late model slick = compound + psi management"), mfr, { key: "grt" } )); } if (snap.hasCross && snap.cross != null && snap.cross > 53) { priors.push(signal( "builder_grt_cross_walk", "GRT signal: cross walking up on file \u2014 check RF wedge and stagger before adding rear weight; GRT sheet assumes stable cross through heat", tagReason(ADVANCED_SOURCE.PRINCIPLE, "Cross drift often front-side, not LR ballast"), mfr, { key: "grt" } )); } return priors; } function harrisSignals(snap, track, mfr) { const priors = []; priors.push(signal( "builder_harris_jbar", "Harris/modified signal: J-bar + panhard height set platform before 4-link bite chase \xB7 modified torque wants mechanical balance, not stacked LR weight", tagReason(ADVANCED_SOURCE.INDUSTRY, "Harris manual anchor \u2014 rear geometry order"), mfr, { key: "harris" } )); if (/slick|drying/.test(track) && snap.hasCross) { priors.push(signal( "builder_harris_slick_stagger", "Harris slick signal: stagger out 1/8\u20131/4 in before panhard/J-bar hero move \xB7 IMCA modified slick nights flip from tacky baseline quickly", tagReason(ADVANCED_SOURCE.PRINCIPLE, "Modified slick lever order"), mfr, { key: "harris" } )); } return priors; } function genericLateModelSignals(snap, track, mfr, key, enrichedVc = {}) { if (key !== "latemodel_generic" && key !== "modified_generic") return []; const priors = [ signal( "builder_lm_generic", "Late model signal: log cross + hot psi every session \xB7 wedge OR stagger per A-B-A \u2014 not both same run", tagReason(ADVANCED_SOURCE.PRINCIPLE, "LM discipline without builder ID on file"), mfr, { key } ) ]; if (hasMultiShockRear(enrichedVc)) { priors.push(signal( "builder_lm_multi_shock", "Multi-shock rear on file: slick nights \u2014 tune secondary/can before RF wedge \xB7 primary carries roll, secondary controls bottoming", tagReason(ADVANCED_SOURCE.INDUSTRY, "Dual rear damping lever order on your platform"), mfr, { key } )); } const plat = readLmModPlatform(enrichedVc); if (plat.compact_trailing_arm === "compact") { priors.push(signal( "builder_lm_compact_arm", "Compact trailing-arm package: one panhard or J-bar hole per run \u2014 geometry responds faster than long-arm cars", tagReason(ADVANCED_SOURCE.PRINCIPLE, "Short trailing-arm sensitivity"), mfr, { key } )); } return priors; } function buildBuilderSignalPriors(enrichedVc = {}, trackState, _trackCondition = {}, opts = {}) { const profile2 = opts.profile || enrichedVc.advanced_profile || ""; const builder = detectBuilderKey(enrichedVc, profile2); if (!builder.key || builder.confidence === "missing") return []; const snap = setupSnapshot(enrichedVc); const track = String(trackState || "").toLowerCase(); const mfr = builder.mfr || "Builder"; let priors = []; switch (builder.key) { case "mach1": priors = mach1Signals(snap, track, mfr); break; case "jj": priors = jjSignals(snap, track, mfr); break; case "krk": priors = krkSignals(snap, track, mfr); break; case "fletcher": priors = fletcherSignals(snap, mfr); break; case "grt": priors = grtSignals(snap, track, mfr); break; case "harris": priors = harrisSignals(snap, track, mfr); break; case "latemodel_generic": case "modified_generic": priors = genericLateModelSignals(snap, track, mfr, builder.key, enrichedVc); break; default: break; } const max = opts.maxPriors ?? 2; return priors.slice(0, max); } // scripts/lib/experimental/abaTestingDiscipline.mjs var ABA_TAG = "[A-B-A DISCIPLINE]"; var ABA_PROTOCOL = { lever: "aba_protocol_clean", action: "Clean A-B-A tonight: Run A = baseline (same line, 3+ laps) \xB7 change ONE lever \xB7 Run B = same line/pace \xB7 Run A\u2032 = revert OR confirm B before booking", reason: `${ABA_TAG} One variable per test \u2014 stacked changes erase cause/effect in Crew Chief` }; var ABA_SAME_STATE = { lever: "aba_same_track_state", action: "A-B-A only counts on the same track state \u2014 do not compare tacky baseline to slick heat 2; log state (tacky/slick/rubber) on every row", reason: `${ABA_TAG} Track evolution invalidates cross-session compares unless state matches` }; var ABA_INTERPRET_HIGH = { lever: "aba_interpret_high", action: "Your HIGH confidence finding survived correction \u2014 book it on the scale sheet, then test the NEXT lever in a fresh A-B-A (do not stack on the same night until A\u2032 confirms)", reason: `${ABA_TAG} HIGH = repeatable signal on your car \u2014 treat as booked until disproven` }; var ABA_INTERPRET_MODERATE = { lever: "aba_interpret_moderate", action: "MODERATE finding \u2014 real but not bulletproof: re-run a clean A-B-A on the next similar track state before changing another lever; note confounds (line, traffic, tire age)", reason: `${ABA_TAG} MODERATE = promising \u2014 confirm before building a feature setup on it` }; var ABA_NO_FINDINGS = { lever: "aba_build_baseline", action: "No logged findings yet \u2014 build your baseline row tonight (scale + feel + track state) then one A-B-A change; public priors are scaffolding until your runs replace them", reason: `${ABA_TAG} Your data upgrades every recommendation below` }; var ABA_RACE_NIGHT = { lever: "aba_race_night_pace", action: "Race night pace: heat = confirm baseline \xB7 main/consi = ONE committed lever from A-B-A \xB7 feature = protect tires \u2014 no new A-B-A hero tests unless you revert first", reason: `${ABA_TAG} Late sessions punish unconfirmed experiments` }; var ABA_LOG_THE_CHANGE = { lever: "aba_log_hypothesis", action: 'Write the hypothesis before the change: "LR bar \u22121 turn \u2192 exit loose" \xB7 after Run B log pass/fail by phase \xB7 if inconclusive, revert and retry same lever smaller step', reason: `${ABA_TAG} Hypothesis + outcome is how Crew Chief learns your car` }; function abaTestingDisciplinePriors(opts = {}) { const { hasFindings = false, topLevel = null, nFind = 0, lateNight = false } = opts; const priors = []; if (hasFindings && topLevel === "HIGH") { priors.push(ABA_INTERPRET_HIGH); } else if (hasFindings && (topLevel === "MODERATE" || nFind > 0)) { priors.push(ABA_INTERPRET_MODERATE); } else if (!hasFindings) { priors.push(ABA_NO_FINDINGS); } priors.push(ABA_PROTOCOL); priors.push(ABA_SAME_STATE); if (lateNight || hasFindings) priors.push(ABA_RACE_NIGHT); if (hasFindings) priors.push(ABA_LOG_THE_CHANGE); return priors.slice(0, 4); } function advancedAbaInterpretationPrior(hasFindings, topFinding = null) { if (!hasFindings) return null; const level = topFinding?.level || "MODERATE"; return { lever: "advanced_aba_interpret", action: level === "HIGH" ? "Logged finding is HIGH \u2014 geometry/wing move is booked for this track state; next session test tire or stagger only in a fresh A-B-A" : "Logged finding is MODERATE \u2014 repeat A-B-A on same state before pairing wing + bar same run", reason: tagReason(ADVANCED_SOURCE.INDUSTRY, "Advanced builders need clean cause chains in the log") }; } // scripts/lib/experimental/publicBaselinePriors.mjs var HYPER_MIDGET_BASELINE = { tire_psi: { lf: "9-10", rf: "9-11", lr: "5-7", rr: "6-10" }, stagger_in: { start: "4", range: "2.5-6" }, ride_height_in: "3.25-3.5 (no driver, verify on your car)", shock_adj: "full stiff minus ~2 turns out (4 clicks) per corner as a starting band" }; var PUBLIC_BASELINE_CAVEAT = " Public baseline / starting point from manufacturer guidance \u2014 validate with your own A-B-A data."; var PUBLIC_BASELINE_SOURCE = "Hyper Racing midget setup guide (public)"; function isEventModeBc39(trackCondition) { const ec = trackCondition?.event_context; return ec?.mode === "bc39" || ec?.event === "BC39"; } function normalizeSurfaceState15(trackState) { const s = String(trackState || "").toLowerCase(); if (["dry_slick", "slick", "heavy"].includes(s)) return "slick"; if (s === "drying") return "drying"; if (["tacky", "rubbered"].includes(s)) return "tacky"; if (["greasy", "wet"].includes(s)) return "greasy"; return "unknown"; } var PSI6 = HYPER_MIDGET_BASELINE.tire_psi; var STG6 = HYPER_MIDGET_BASELINE.stagger_in; var PRIORS_BY_BUCKET14 = { tacky: [ { lever: "tire_pressure", action: `Log starting pressures before changes: LF ${PSI6.lf} / RF ${PSI6.rf} / LR ${PSI6.lr} / RR ${PSI6.rr} psi (1 psi steps)`, reason: "public baseline (Hyper): tacky usually carries more psi than slick; right-side stiffer tends to loosen, left-side stiffer tends to tighten" }, { lever: "stagger", action: `Start near ${STG6.start} in stagger (typical band ${STG6.range} in) \u2014 remeasure hot after laps 8-10`, reason: "public baseline (Hyper): stagger is a primary balance lever \u2014 more stagger loosens, less stagger tighten; change in 1/4 in steps only" }, { lever: "shock_adj", action: `Adjustable shocks: ${HYPER_MIDGET_BASELINE.shock_adj} \u2014 then one click at a time`, reason: "public baseline (Hyper): start conservative on clickers; use shock gas psi as fine-tune only, not a spring substitute" }, { lever: "weight", action: "If loose through the middle/exit on a small track, try a little LR + RF weight before big shock moves", reason: "public baseline (Hyper): cross-weight toward LR-RF can tighten mid/exit without hurting entry much on short tracks" }, { lever: "geometry", action: "Verify axle squareness and bearing carrier timing before chasing setup", reason: "public baseline (Hyper): square axles and consistent carrier timing \u2014 bad geometry masks real setup learning" }, { lever: "discipline", action: "One lever per run \u2014 do not stack bar, shock, and pressure in the same heat", reason: "public baseline (Hyper): isolate variables so logged A-B-A can replace these priors quickly" } ], drying: [ { lever: "stagger", action: `As sheen goes away, take 1/4 in stagger out (toward ${STG6.range} in low end) before shock chasing`, reason: "public baseline (Hyper): slick transition usually wants less stagger first \u2014 frees the center as rubber builds" }, { lever: "rr", action: "Ease RR pressure down as the track frees (1 psi steps toward the low end of the RR band)", reason: "public baseline (Hyper): to tighten \u2014 lower RR psi; protect RR bite for drive as track slicks" }, { lever: "tire_pressure", action: "Transition all corners down slightly with track state \u2014 log pyrometer each session", reason: "public baseline (Hyper): drying tracks usually want less psi than tacky; tire spring rate shifts with pressure" }, { lever: "ride_height", action: "If entry push appears while drying, try a small ride-height raise (1 turn) before big shock changes", reason: "public baseline (Hyper): raising ride height can tighten and help forward bite on smaller tracks \u2014 verify no bottoming" }, { lever: "comp", action: "If hop shows up while drying, soften HS compression before adding rebound", reason: "public baseline (Hyper): stiff compression on a freeing track can add hop \u2014 address comp before reb" } ], slick: [ { lever: "stagger", action: `To tighten on slick: reduce stagger toward ${STG6.range} in low end (1/4 in steps)`, reason: "public baseline (Hyper): less stagger tighten; existing slick sequence also pulls stagger down as rubber builds" }, { lever: "rr", action: "To tighten on slick: lower RR psi (1 psi steps) and protect RR for drive off", reason: "public baseline (Hyper): lower RR pressure is a primary tighten move on slick \u2014 do not stack with stagger same run" }, { lever: "ride_height", action: "To tighten mid/exit: try a small ride-height raise (1 turn all corners) before panhard or bar changes", reason: "public baseline (Hyper): raising ride height can add forward bite on small tracks \u2014 watch for bottoming on entry" }, { lever: "panhard", action: "Panhard/J-bar: raise front bar or lower rear bar to tighten; opposite to loosen (one hole at a time)", reason: "public baseline (Hyper): roll-center height shifts balance entry-to-exit \u2014 small bar moves only" }, { lever: "comp", action: "To loosen on slick: stiffen RR compression slightly OR soften LR rebound \u2014 not both same run", reason: "public baseline (Hyper): RR comp up loosens mid/entry; LR reb down can loosen entry \u2014 one lever at a time" }, { lever: "reb", action: "If hop persists, check LF/LR rebound balance before chasing more bite", reason: "public baseline (Hyper): hop often ties to too much RR dynamic weight \u2014 LF reb up / LR reb down can settle the platform" } ], greasy: [ { lever: "stagger", action: "On heavy/greasy tracks, more stagger can help loosen \u2014 go up in 1/4 in steps from baseline", reason: "public baseline (Hyper): to loosen \u2014 increase stagger; wet/greasy often wants a wider band before shock work" }, { lever: "tire_pressure", action: `Run higher psi band on wet vs slick (RR toward upper ${PSI6.rr} range) \u2014 still 1 psi per change`, reason: "public baseline (Hyper): tacky/wet usually wants more pressure than slick; log and adjust after each session" }, { lever: "comp", action: "Soften front compression on wet entry \u2014 stiff nose tends to push", reason: "public baseline (Hyper): greasy tracks reward a softer front platform so the car rotates on entry" }, { lever: "reb", action: "Less LR tie-down can tighten entry on wet \u2014 balance against hop through the middle", reason: "public baseline (Hyper): LR rebound/tie-down is an entry lever \u2014 too much tie-down can make the car hop" }, { lever: "rr_offset", action: "To loosen on heavy track: move RR out slightly \u2014 verify axle length and carrier timing first", reason: "public baseline (Hyper): RR out can loosen; square axles before offset changes" } ], unknown: [ { lever: "track_state", action: "Log track state each session (tacky \u2192 drying \u2192 slick) before booking setup moves", reason: "public baseline (Hyper): recommendations stratify by surface \u2014 unlogged state blocks real findings" }, { lever: "tire_pressure", action: `Establish baseline band: LF ${PSI6.lf} / RF ${PSI6.rf} / LR ${PSI6.lr} / RR ${PSI6.rr} psi on first clean laps`, reason: "public baseline (Hyper): tire psi acts like spring rate \u2014 right-side stiffer loosens, left-side stiffer tighten" }, { lever: "stagger", action: `Start near ${STG6.start} in (${STG6.range} in band) \u2014 measure hot stagger after laps 8-10`, reason: "public baseline (Hyper): remeasure before the next move; stagger growth through the night is normal" }, { lever: "shock_adj", action: HYPER_MIDGET_BASELINE.shock_adj, reason: "public baseline (Hyper): start conservative; shock gas psi is fine-tune only" }, { lever: "discipline", action: "One DOF per run \u2014 confirm with A-B-A before the feature", reason: "public baseline (Hyper): record every change with track state and driver feedback" } ] }; function hyperMidgetBaselinePriors(trackState, opts = {}) { const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState15(trackState); const list = PRIORS_BY_BUCKET14[bucket] || PRIORS_BY_BUCKET14.unknown; return list.filter((p) => !skip.has(p.lever)); } function resolvePublicBaselineProfile(trackCondition = {}, opts = {}) { const vc2 = trackCondition?.vehicle_context || {}; const cls = String(vc2.car_class || vc2.class_name || opts.car_class || "").toLowerCase(); const carType = String(vc2.car_type || opts.car_type || "").toLowerCase(); if (carType === "quartermidget" || /quarter.midget|qma|\bqm\b/.test(cls)) { return "quarter_midget"; } if (carType === "outlawkart" || /outlaw.*kart|cage kart|f1 outlaw|outlaw\s*(125|250|500)/.test(cls) && !/quarter midget|qma/.test(cls)) { return "outlaw_kart"; } if (isModLiteProfile(vc2, cls, carType)) { return "mod_lite"; } if (isMicroTurfProfile(vc2, cls, carType)) { return "micro_turf"; } if (isJuniorSprintProfile(vc2, cls, carType)) { return "junior_sprint"; } if (isMicro270Profile(vc2, cls, carType)) { return "micro_sprint_270"; } if (isMicroSprint600Profile(vc2, cls, carType)) { return "micro_sprint_600"; } if (isLightningSprintProfile(vc2, cls, carType)) { return "lightning_sprint"; } if (isFlatKartProfile(vc2, cls, carType)) { return "flat_kart"; } if (carType === "latemodel" || /late.model|super.late|602 crate|604 crate|crate late|limited.lm|pro late|slm|lucas oil|woo.*late|dirtcar late|xr super|fastrak/i.test(cls)) { return "late_model"; } if (isSprintSportsmanProfile(vc2, cls, carType)) { return "sprint_360_sportsman"; } if (carType === "modified" || /imca modified|ump modified|sport mod|b-mod|wissota mod|358 mod|a-mod/i.test(cls)) { return "modified_dirt"; } if (/midget/.test(cls) && !/micro|stock|quarter/.test(cls)) return "hyper_midget"; if (/305|360|\bsprint\b|410|ascs|racesaver|glss|winged sprint/.test(cls)) return "maxim_sprint_winged"; if (carType === "sprint") return "maxim_sprint_winged"; if (/maxim|beast|gaerte|k-car|kls|mach[\s-]?1|mach1|\bj&j\b|\bjj\b|\bkrk\b|keen speed|fletcher/.test(String(vc2.chassis_name || "").toLowerCase())) { if (/305|360|\bsprint\b|410|ascs|racesaver|glss|winged sprint/.test(cls) || carType === "sprint") { return "maxim_sprint_winged"; } if (/midget/.test(cls) && !/micro|stock|quarter/.test(cls)) return "hyper_midget"; } return "hyper_midget"; } function finishGrassrootsBaseline(profile2, result, trackState, skip) { if (!isGrassrootsDirtOvalProfile(profile2)) { return { ...result, commonPriors: [], commonCaveat: null, commonSource: null, dayOnePriors: [], dayOneCaveat: null, dayOneSource: null, surfacePriors: [], surfaceCaveat: null, surfaceSource: null, surfaceBucket: null, contextPriors: [], contextGapPriors: [], contextAssessment: null, firstMovePriors: [], guardrailPriors: [], guardrailsCaveat: null, tirePriors: [], tireCaveat: null, scalingPriors: [], scalingCaveat: null, handlingPriors: [], handlingCaveat: null, diagnosedSymptom: null, shockPriors: [], shockCaveat: null, practicePriors: [], practiceCaveat: null, racePriors: [], raceCaveat: null, loggingPriors: [], loggingCaveat: null }; } const commonPriors = mergeGrassrootsCommonPriors(result.priors, trackState, skip); const used = /* @__PURE__ */ new Set([ ...skip, ...result.priors.map((p) => p.lever), ...(result.chassisPriors || []).map((p) => p.lever), ...commonPriors.map((p) => p.lever) ]); const dayOnePriors = isGrassrootsDayOneProfile(profile2) ? grassrootsDayOnePriors(trackState, { skipLevers: used, profile: profile2 }) : []; dayOnePriors.forEach((p) => used.add(p.lever)); const surfaceBucket = isGrassrootsDayOneProfile(profile2) ? normalizeGrassrootsSurface(trackState) : "unknown"; const surfacePriors = isGrassrootsDayOneProfile(profile2) ? grassrootsSurfaceAdjustments(trackState, profile2, { skipLevers: used }) : []; surfacePriors.forEach((p) => used.add(p.lever)); const tireBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsTirePressureGuidance(trackState, profile2, { skipLevers: used }) : { tirePriors: [], surfaceBucket: "unknown" }; (tireBlock.tirePriors || []).forEach((p) => used.add(p.lever)); const scalingBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsScalingGuidance(trackState, profile2, { skipLevers: used }) : { scalingPriors: [], surfaceBucket: "unknown" }; (scalingBlock.scalingPriors || []).forEach((p) => used.add(p.lever)); const handlingBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsHandlingDiagnosis(trackState, profile2, result.vehicle_context || {}, { skipLevers: used }) : { handlingPriors: [], diagnosedSymptom: null }; (handlingBlock.handlingPriors || []).forEach((p) => used.add(p.lever)); if (handlingBlock.handlingPriors?.length) used.add("your_symptom"); const shockBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsShockGuidance(trackState, profile2, result.vehicle_context || {}, { skipLevers: used }) : { shockPriors: [], surfaceBucket: "unknown" }; (shockBlock.shockPriors || []).forEach((p) => used.add(p.lever)); const practiceBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsPracticeStrategy(trackState, profile2, { skipLevers: used }) : { practicePriors: [], surfaceBucket: "unknown" }; (practiceBlock.practicePriors || []).forEach((p) => used.add(p.lever)); const raceBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsRaceNightStrategy(trackState, profile2, { skipLevers: used }) : { racePriors: [], surfaceBucket: "unknown" }; (raceBlock.racePriors || []).forEach((p) => used.add(p.lever)); const loggingBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsLoggingGuidance(trackState, profile2, { skipLevers: used }) : { loggingPriors: [] }; (loggingBlock.loggingPriors || []).forEach((p) => used.add(p.lever)); const ctxBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsContextPersonalizationPriors(profile2, result.vehicle_context || {}, trackState, { skipLevers: used }) : { priors: [], gapPriors: [], assessment: null }; const moveBlock = isGrassrootsDayOneProfile(profile2) ? grassrootsFirstMoveGuardrails(trackState, profile2, { skipLevers: used }) : { firstMovePriors: [], guardrailPriors: [] }; return { ...result, commonPriors, commonCaveat: GRASSROOTS_DIRT_OVAL_COMMON_CAVEAT, commonSource: GRASSROOTS_COMMON_SOURCE, dayOnePriors, dayOneCaveat: dayOnePriors.length ? GRASSROOTS_DAY_ONE_CAVEAT : null, dayOneSource: dayOnePriors.length ? GRASSROOTS_DAY_ONE_SOURCE : null, surfacePriors, surfaceCaveat: surfacePriors.length ? GRASSROOTS_SURFACE_CAVEAT : null, surfaceSource: surfacePriors.length ? GRASSROOTS_SURFACE_SOURCE : null, surfaceBucket: surfacePriors.length ? surfaceBucket : null, tirePriors: tireBlock.tirePriors || [], tireCaveat: tireBlock.tirePriors?.length ? GRASSROOTS_TIRE_CAVEAT : null, tireSource: tireBlock.tirePriors?.length ? GRASSROOTS_TIRE_SOURCE : null, scalingPriors: scalingBlock.scalingPriors || [], scalingCaveat: scalingBlock.scalingPriors?.length ? GRASSROOTS_SCALING_CAVEAT : null, scalingSource: scalingBlock.scalingPriors?.length ? GRASSROOTS_SCALING_SOURCE : null, handlingPriors: handlingBlock.handlingPriors || [], handlingCaveat: handlingBlock.handlingPriors?.length ? GRASSROOTS_HANDLING_CAVEAT : null, handlingSource: handlingBlock.handlingPriors?.length ? GRASSROOTS_HANDLING_SOURCE : null, diagnosedSymptom: handlingBlock.diagnosedSymptom || null, shockPriors: shockBlock.shockPriors || [], shockCaveat: shockBlock.shockPriors?.length ? GRASSROOTS_SHOCK_CAVEAT : null, shockSource: shockBlock.shockPriors?.length ? GRASSROOTS_SHOCK_SOURCE : null, practicePriors: practiceBlock.practicePriors || [], practiceCaveat: practiceBlock.practicePriors?.length ? GRASSROOTS_PRACTICE_CAVEAT : null, practiceSource: practiceBlock.practicePriors?.length ? GRASSROOTS_PRACTICE_SOURCE : null, racePriors: raceBlock.racePriors || [], raceCaveat: raceBlock.racePriors?.length ? GRASSROOTS_RACE_CAVEAT : null, raceSource: raceBlock.racePriors?.length ? GRASSROOTS_RACE_SOURCE : null, loggingPriors: loggingBlock.loggingPriors || [], loggingCaveat: loggingBlock.loggingPriors?.length ? GRASSROOTS_LOGGING_CAVEAT : null, loggingSource: loggingBlock.loggingPriors?.length ? GRASSROOTS_LOGGING_SOURCE : null, contextPriors: ctxBlock.priors || [], contextGapPriors: ctxBlock.gapPriors || [], contextAssessment: ctxBlock.assessment || null, contextPersonalizedCaveat: GRASSROOTS_PERSONALIZED_CAVEAT, contextGapCaveat: GRASSROOTS_CONTEXT_GAP_CAVEAT, firstMovePriors: moveBlock.firstMovePriors || [], guardrailPriors: moveBlock.guardrailPriors || [], guardrailsCaveat: moveBlock.firstMovePriors?.length || moveBlock.guardrailPriors?.length ? GRASSROOTS_GUARDRAILS_CAVEAT : null, firstMoveSource: GRASSROOTS_FIRST_MOVE_SOURCE, guardrailSource: GRASSROOTS_GUARDRAIL_SOURCE, chassisPriors: result.chassisPriors || [], vehicle_context: result.vehicle_context || null }; } function buildProfileBaseline(profile2, trackState, skip, vc2, classPriorsFn, source, caveat) { const enriched = enrichGrassrootsVehicleContext(vc2, profile2); const rawChassis = grassrootsChassisManufacturerPriors(profile2, enriched, trackState); const chassisPriors = augmentWithThinDataLayer(profile2, enriched, trackState, rawChassis).filter((p) => !skip.has(p.lever)); const chassisDeepCaveat = resolveGrassrootsChassisDeepCaveat(profile2, enriched) || resolveThinDataDeepCaveat(profile2, enriched); const classSkip = /* @__PURE__ */ new Set([...skip, ...chassisPriors.map((p) => p.lever)]); const priors = classPriorsFn(trackState, { skipLevers: classSkip, vehicleContext: enriched }); return finishGrassrootsBaseline(profile2, { priors, chassisPriors, chassisDeepCaveat, source, profile: profile2, caveat, vehicle_context: enriched }, trackState, skip); } function buildAdvancedProfileBaseline(profile2, trackState, skip, vc2, classPriorsFn, source, caveat, trackCondition = {}) { const enriched = enrichAdvancedVehicleContext(vc2, profile2); const geometryPriors = advancedSprintGeometryPriors(trackState, { profile: profile2 }).filter((p) => !skip.has(p.lever)); const advancedPriors = advancedChassisManufacturerPriors(profile2, enriched, trackState).filter((p) => !skip.has(p.lever)); const grassrootsHyperPriors = profile2 === "hyper_midget" && !isAdvancedDeepBuilder(enriched.chassis_mfr_key) ? augmentWithThinDataLayer( profile2, enrichGrassrootsVehicleContext(vc2, profile2), trackState, grassrootsChassisManufacturerPriors(profile2, enrichGrassrootsVehicleContext(vc2, profile2), trackState) ).filter((p) => !skip.has(p.lever)) : []; const chassisPriors = augmentWithThinDataLayer( profile2, enriched, trackState, [...geometryPriors, ...advancedPriors, ...grassrootsHyperPriors] ).filter((p) => !skip.has(p.lever)); const chassisDeepCaveat = resolveAdvancedChassisDeepCaveat(profile2, enriched) || resolveGrassrootsChassisDeepCaveat(profile2, enrichGrassrootsVehicleContext(vc2, profile2)) || resolveThinDataDeepCaveat(profile2, enriched); const classSkip = /* @__PURE__ */ new Set([...skip, ...chassisPriors.map((p) => p.lever)]); const priors = classPriorsFn(trackState, { skipLevers: classSkip, vehicleContext: enriched }); const ctxBlock = buildAdvancedContextBlock(enriched, trackState, profile2, classSkip); const loggingPriors = advancedChassisLoggingPriors(trackState, enriched).filter((p) => !skip.has(p.lever) && !classSkip.has(p.lever)); const betweenSessionPriors = advancedBetweenSessionPriors(enriched, trackState).filter((p) => !skip.has(p.lever) && !classSkip.has(p.lever)); const evolutionSkip = /* @__PURE__ */ new Set([...classSkip, ...betweenSessionPriors.map((p) => p.lever)]); const trackEvolutionPriors = advancedTrackEvolutionPriors(enriched, trackState, { vehicle_context: enriched, session_log: trackCondition.session_log || vc2.session_log, track_snaps: trackCondition.track_snaps || vc2.track_snaps, line_progression: trackCondition.line_progression || trackCondition.line || vc2.line_progression, drying_prediction: trackCondition.drying_prediction || trackCondition.drying }, { skipLevers: evolutionSkip, profile: profile2 }).filter((p) => !skip.has(p.lever) && !evolutionSkip.has(p.lever)); const finished = finishGrassrootsBaseline(profile2, { priors, chassisPriors, chassisDeepCaveat, source, profile: profile2, caveat, vehicle_context: enriched, advanced_chassis_layer: advancedPriors.length > 0, advanced_geometry_layer: geometryPriors.length > 0 }, trackState, skip); return { ...finished, advanced_geometry_layer: geometryPriors.length > 0, advancedLoggingPriors: loggingPriors, advancedBetweenSessionPriors: betweenSessionPriors, advancedTrackEvolutionPriors: trackEvolutionPriors, advancedLoggingCaveat: loggingPriors.length ? ADVANCED_LOGGING_CAVEAT : null, advancedLoggingSource: loggingPriors.length ? ADVANCED_LOGGING_SOURCE : null, contextPriors: ctxBlock.priors, contextGapPriors: ctxBlock.gapPriors, contextAssessment: ctxBlock.assessment, contextPersonalizedCaveat: ADVANCED_PERSONALIZED_CAVEAT, contextGapCaveat: ADVANCED_CONTEXT_GAP_CAVEAT, advanced_context_layer: true }; } function attachTrackSnapPriors(baseline, trackCondition, trackState) { const trackSnapPriors = buildUniversalTrackSnapPriors(trackCondition, trackState); return trackSnapPriors.length ? { ...baseline, trackSnapPriors } : baseline; } function attachEvolutionPriors(baseline, trackCondition, trackState) { const profile2 = baseline.profile; const vc2 = trackCondition?.vehicle_context || baseline.vehicle_context || {}; const enriched = baseline.vehicle_context || vc2; const evoCtx = buildEnrichedEvolutionContext( { ...trackCondition, vehicle_context: enriched }, trackState ); let next = { ...baseline, evoCtx }; if (isAdvancedChassisProfile(profile2)) { next.advancedTrackEvolutionPriors = advancedTrackEvolutionPriors( enriched, trackState, trackCondition, { profile: profile2, evoCtx } ); } else if (isLateModelEvolutionProfile(profile2)) { next.lateModelTrackEvolutionPriors = lateModelTrackEvolutionPriors( enriched, trackState, trackCondition, { profile: profile2, evoCtx } ); next.lmModModernPriors = lmModModernSetupPriors(enriched, trackState, profile2); } const msPriors = multiShockDamperPriors(enriched, profile2); if (msPriors.length) next.multiShockDamperPriors = msPriors; return next; } function attachBuilderAndAbaPriors(baseline, trackCondition, trackState, opts = {}) { const enriched = baseline.vehicle_context || trackCondition?.vehicle_context || {}; const builderSignalPriors = buildBuilderSignalPriors( enriched, trackState, trackCondition, { profile: baseline.profile, maxPriors: 2 } ); const abaDisciplinePriors = abaTestingDisciplinePriors(opts); let next = baseline; if (builderSignalPriors.length) next = { ...next, builderSignalPriors }; if (abaDisciplinePriors.length) next = { ...next, abaDisciplinePriors }; return next; } function finalizeBaseline(baseline, trackCondition, trackState, abaOpts = {}) { return attachBuilderAndAbaPriors( attachEvolutionPriors(attachTrackSnapPriors(baseline, trackCondition, trackState), trackCondition, trackState), trackCondition, trackState, abaOpts ); } function resolvePublicBaseline(trackState, trackCondition = {}, opts = {}) { const profile2 = resolvePublicBaselineProfile(trackCondition, opts); const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const vc2 = trackCondition?.vehicle_context || {}; let baseline; if (profile2 === "outlaw_kart") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, outlawKartBaselinePriors, OUTLAW_KART_BASELINE.source, OUTLAW_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "quarter_midget") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, quarterMidgetBaselinePriors, QUARTER_MIDGET_BASELINE.source, QM_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "micro_sprint_600") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, microSprint600BaselinePriors, MICRO_SPRINT_600_BASELINE.source, MICRO_SPRINT_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "junior_sprint") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, juniorSprintBaselinePriors, JUNIOR_SPRINT_BASELINE.source, JUNIOR_SPRINT_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "micro_sprint_270") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, micro270BaselinePriors, MICRO_270_BASELINE.source, MICRO_270_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "micro_turf") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, microTurfBaselinePriors, MICRO_TURF_BASELINE.source, MICRO_TURF_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "mod_lite") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, modLiteBaselinePriors, MOD_LITE_BASELINE.source, MOD_LITE_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "sprint_360_sportsman") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, sprintSportsmanBaselinePriors, SPRINT_SPORTSMAN_BASELINE.source, SPRINT_SPORTSMAN_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "lightning_sprint") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, lightningSprintBaselinePriors, LIGHTNING_SPRINT_BASELINE.source, LIGHTNING_SPRINT_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "flat_kart") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, flatKartBaselinePriors, FLAT_KART_BASELINE.source, FLAT_KART_PUBLIC_BASELINE_CAVEAT); } else if (profile2 === "maxim_sprint_winged") { baseline = buildAdvancedProfileBaseline(profile2, trackState, skip, vc2, maximSprintWingedBaselinePriors, MAXIM_WINGED_SPRINT_BASELINE.source, MAXIM_PUBLIC_BASELINE_CAVEAT, trackCondition); } else if (profile2 === "hyper_midget" && isAdvancedChassisProfile(profile2)) { baseline = buildAdvancedProfileBaseline(profile2, trackState, skip, vc2, hyperMidgetBaselinePriors, PUBLIC_BASELINE_SOURCE, PUBLIC_BASELINE_CAVEAT.trim(), trackCondition); } else if (profile2 === "late_model" || profile2 === "modified_dirt") { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, hyperMidgetBaselinePriors, PUBLIC_BASELINE_SOURCE, PUBLIC_BASELINE_CAVEAT.trim()); } else { baseline = buildProfileBaseline(profile2, trackState, skip, vc2, hyperMidgetBaselinePriors, PUBLIC_BASELINE_SOURCE, PUBLIC_BASELINE_CAVEAT.trim()); } return finalizeBaseline(baseline, trackCondition, trackState); } // scripts/lib/experimental/grassrootsDataOverride.mjs var GRASSROOTS_SCAFFOLD_CAVEAT_SUFFIX = " Secondary context only \u2014 your logged findings take priority; validate with A-B-A."; var GRASSROOTS_DATA_LEADS_NOTE = "Your logged A-B-A data drives recommendations \u2014 public baselines below are secondary context only."; function isGrassrootsLearningProfile(profile2) { return isGrassrootsDayOneProfile(profile2); } function resolveBaselineInjectionMode(profile2, { nFind = 0, hasHigh = false, bc39Mode = false } = {}) { if (bc39Mode || !profile2) return "none"; if (isGrassrootsLearningProfile(profile2)) { if (nFind === 0) return "full"; return "thin"; } return nFind > 0 ? "none" : "full"; } var GRASSROOTS_SCAFFOLD_CAP = 2; var baseId = (study) => String(study || "").split(":")[0]; function genericFindingRec(entry, ctx) { if (!(entry.level === "HIGH" || entry.level === "MODERATE")) return null; if (entry.verdict !== "SIGNAL" || entry.d == null) return null; const id = baseId(entry.study); const headline = String(entry.text || "").split(".")[0].trim(); if (!headline) return null; let reason = entry.text || headline; if (ctx.track_state) reason += ` (track is ${ctx.track_state})`; const caveat = entry.level === "MODERATE" ? "Moderate confidence \u2014 confirm with a clean A-B-A on the same track state." : ""; return { action: `From your runs: ${headline}`, lever: id.toLowerCase(), basis: "finding", confidence: entry.level, refs: [entry.study], reason, caveat, flags: { user_data: true, study_id: id } }; } function grassrootsAbaNudgeRec(hasFindings, topFinding = null) { if (hasFindings) { const hint = topFinding?.text ? ` Top finding: ${String(topFinding.text).split(".")[0]}.` : ""; return { action: "Confirm tonight's logged finding with a clean A-B-A before changing another lever", lever: "aba_confirm", basis: "prior", confidence: "LOW", refs: ["grassroots_aba"], reason: `grassroots A-B-A: one change at a time \u2014 compare before/after on the same track state.${hint}`, caveat: "Discipline beats parts \u2014 re-run baseline then the change once more to book it.", flags: { grassroots_aba: true, user_data: true } }; } return { action: "Learn this car: baseline run \u2192 one change \u2192 confirm run \u2014 log psi, stagger, and lap feel each time", lever: "aba_start", basis: "prior", confidence: "LOW", refs: ["grassroots_aba"], reason: "grassroots A-B-A: public baselines are starting points until your own runs replace them", caveat: "Validate every move with your data \u2014 one lever per run.", flags: { grassroots_aba: true } }; } // scripts/lib/experimental/advancedChassisDataOverride.mjs var ADVANCED_SCAFFOLD_CAP = 2; function isAdvancedLearningProfile(profile2) { return isAdvancedChassisProfile(profile2); } function resolveAdvancedBaselineInjectionMode(profile2, { nFind = 0, bc39Mode = false } = {}) { if (bc39Mode || !profile2 || !isAdvancedChassisProfile(profile2)) return "none"; if (nFind > 0) return "none"; return "full"; } // scripts/lib/experimental/grassrootsPrioritization.mjs var GRASSROOTS_ACTION_REC_CAP = 6; var PRIOR_TIER = { NOW: 1, BASICS: 2, FINE_TUNE: 3, PROCESS: 4, DEFER: 5 }; var PHASE_FIRST = { tight_in_loose_off: "entry", entry_push: "entry", mid_push: "mid", exit_loose: "exit", exit_tight: "exit", center_flat: "mid", wont_turn: "entry", washes_up: "entry" }; function classifyGrassrootsRecTier(r, ctx = {}) { if (r.basis === "finding") return PRIOR_TIER.NOW; if (r.flags?.grassroots_handling && String(r.lever || "").startsWith("handle_fix_")) return PRIOR_TIER.NOW; if (r.flags?.grassroots_first_move) return PRIOR_TIER.BASICS; if (r.flags?.grassroots_personalized) return PRIOR_TIER.BASICS; if (r.flags?.grassroots_context_gap) { return ctx.thinData && (ctx.completeness ?? 0) < 40 ? PRIOR_TIER.BASICS : PRIOR_TIER.PROCESS; } if (r.flags?.grassroots_tire || r.flags?.grassroots_surface || r.flags?.grassroots_scaling) { return PRIOR_TIER.BASICS; } if (r.flags?.chassis_specific && !r.flags?.scaffold) return PRIOR_TIER.BASICS; if (r.flags?.grassroots_handling) return PRIOR_TIER.FINE_TUNE; if (r.flags?.grassroots_shock) { const lev = String(r.lever || ""); if (lev === "shock_guide_philosophy" || lev === "shock_guide_when" || lev === "shock_guide_start" || lev === "shock_guide_cond") { return PRIOR_TIER.PROCESS; } return PRIOR_TIER.FINE_TUNE; } if (r.flags?.grassroots_guardrail) return PRIOR_TIER.FINE_TUNE; if (r.flags?.grassroots_aba) return PRIOR_TIER.BASICS; if (r.flags?.grassroots_practice || r.flags?.grassroots_race || r.flags?.grassroots_logging) { return PRIOR_TIER.PROCESS; } if (r.flags?.grassroots_process || r.flags?.grassroots_common) return PRIOR_TIER.DEFER; if (r.flags?.scaffold || r.flags?.public_baseline) return PRIOR_TIER.DEFER; if (r.flags?.event_prior) return PRIOR_TIER.PROCESS; return PRIOR_TIER.FINE_TUNE; } function grassrootsTierBoost(r, ctx = {}) { const tier = r.flags?.priority_tier ?? classifyGrassrootsRecTier(r, ctx); let boost = 0; if (tier === PRIOR_TIER.FINE_TUNE) boost -= 0.045; if (tier === PRIOR_TIER.PROCESS) boost -= 0.085; if (tier === PRIOR_TIER.DEFER) boost -= 0.11; if (ctx.thinData) { if (r.flags?.grassroots_context_gap && (ctx.completeness ?? 0) < 40) boost += 0.12; if (r.flags?.grassroots_shock && String(r.lever || "").startsWith("shock_guide_") && r.lever !== "shock_guide_philosophy") { boost -= 0.09; } if (String(r.lever || "").startsWith("handle_alt_")) boost -= 0.07; if (r.flags?.scaffold) boost -= 0.05; } if (ctx.hasFindings && r.basis !== "finding") { if (r.flags?.grassroots_handling || r.flags?.scaffold) boost -= 0.03; } if (String(r.lever || "").startsWith("handle_alt_")) boost -= 0.065; if (r.flags?.grassroots_handling && r.lever === "handle_discipline") boost -= 0.075; if (r.flags?.grassroots_practice || r.flags?.grassroots_race || r.flags?.grassroots_logging) boost -= 0.055; if (r.lever === "tire_guide_priority" || r.lever === "scale_guide_priority") boost -= 0.025; return boost; } function pickGrassrootsTryFirst(recs, ctx = {}) { if (!recs.length) return { rec: null, reason: null }; if (ctx.hasFindings) { const finding = recs.find((r) => r.basis === "finding"); if (finding) { return { rec: finding, reason: "Your logged A-B-A finding leads \u2014 confirm or extend it before chasing new levers." }; } } if (ctx.thinData && (ctx.completeness ?? 0) < 35 && !ctx.hasFeel) { const gap = recs.find((r) => r.flags?.grassroots_context_gap); if (gap) { return { rec: gap, reason: "Thin data \u2014 add chassis and psi in Setup first so tire/handling advice targets YOUR car." }; } } const primaryHandle = recs.find( (r) => r.flags?.grassroots_handling && String(r.lever || "").startsWith("handle_fix_") ); if (primaryHandle && ctx.hasFeel) { const key = ctx.diagnosedSymptom?.key; const phaseHint = PHASE_FIRST[key] ? ` Fix ${PHASE_FIRST[key]} phase first \u2014 alternates and exit moves can wait.` : ""; return { rec: primaryHandle, reason: `Logged feel (${ctx.diagnosedSymptom?.label || "handling"}) \u2014 one primary fix, then re-run.${phaseHint}` }; } const chassisTop = recs.find((r) => r.flags?.chassis_specific && !r.flags?.scaffold); if (chassisTop && ctx.thinData && !ctx.hasFeel) { return { rec: chassisTop, reason: "Your chassis manufacturer baseline leads when data is thin \u2014 one sheet move before generic lists." }; } const firstMove = recs.find((r) => r.flags?.grassroots_first_move); if (firstMove && ctx.thinData) { return { rec: firstMove, reason: "No logged runs yet \u2014 high-impact basics (psi, stagger, ride height) before shocks or fine geometry." }; } const personal = recs.find((r) => r.flags?.grassroots_personalized); if (personal) { return { rec: personal, reason: "Your on-file measurements point here \u2014 one step from your baseline sheet." }; } const tireStart = recs.find((r) => r.lever === "tire_guide_start" || r.flags?.grassroots_tire && r.lever === "tire_pressure"); if (tireStart && ctx.thinData) { return { rec: tireStart, reason: "Start from public psi band \u2014 log hot numbers before wing, bar, or shock fine-tune." }; } const basics = recs.find((r) => (r.flags?.priority_tier ?? 99) <= PRIOR_TIER.BASICS); if (basics) { return { rec: basics, reason: "Highest-impact basic lever from class baseline \u2014 one change per run." }; } return { rec: recs[0], reason: "Top ranked move from available guidance." }; } function buildGrassrootsCanWait(recs, tryRec, ctx = {}) { const skip = tryRec?.lever; const out = []; for (const r of recs) { if (!r || r.lever === skip) continue; const wait = String(r.lever || "").startsWith("handle_alt_") || r.flags?.grassroots_shock && String(r.lever || "").startsWith("shock_guide_") && !["shock_guide_philosophy", "shock_guide_when"].includes(r.lever) || r.flags?.grassroots_practice || r.flags?.grassroots_race || r.flags?.grassroots_logging || r.flags?.scaffold || r.lever === "handle_discipline"; if (!wait) continue; let label = "later"; if (String(r.lever || "").startsWith("handle_alt_")) label = "alt handling"; else if (r.flags?.grassroots_shock) label = "shock fine-tune"; else if (r.flags?.scaffold) label = "scaffold baseline"; else if (r.flags?.grassroots_practice || r.flags?.grassroots_race) label = "session plan"; out.push({ lever: r.lever, label, action: String(r.action || "").slice(0, 72) }); if (out.length >= 3) break; } if (ctx.thinData && !ctx.hasFindings) { out.push({ lever: "defer_shocks", label: "after basics", action: "Shocks and detailed geometry wait until psi/stagger are logged stable" }); } return out.slice(0, 4); } function buildGrassrootsPrioritizationNote(recs, tryRec, ctx, tryReason, canWait = []) { if (!tryRec) return null; const parts = []; if (tryReason) parts.push(tryReason); if (ctx.hasFindings && tryRec.basis !== "finding") { parts.push("Logged findings still outrank public baseline hints."); } if (ctx.thinData && !ctx.hasFindings && (tryRec.flags?.grassroots_tire || tryRec.flags?.grassroots_first_move)) { parts.push("Psi and stagger before shocks \u2014 conservative grassroots order."); } if (canWait.some((w) => w.label === "shock fine-tune" || w.lever === "defer_shocks")) { parts.push("Shock and alt handling fixes can wait until basics are logged."); } return parts.filter(Boolean).join(" "); } function applyGrassrootsPrioritization(recs, ctx, recWeightFn) { const empty = { tryFirst: null, tryFirstReason: null, canWait: [], prioritizationNote: null, actionRecommendations: recs, processRecommendations: [] }; if (!ctx.profile || !isGrassrootsLearningProfile(ctx.profile) || !recs.length) { return empty; } const thinData = ctx.nFind === 0 && ctx.baselineMode !== "none"; const completeness = ctx.contextAssessment?.completeness ?? 0; const pCtx = { ...ctx, thinData, completeness, hasFeel: Boolean(ctx.diagnosedSymptom) }; for (const r of recs) { if (!r.flags) r.flags = {}; r.flags.priority_tier = classifyGrassrootsRecTier(r, pCtx); } recs.sort((a, b) => { const sa = recWeightFn(a) + grassrootsTierBoost(a, pCtx); const sb = recWeightFn(b) + grassrootsTierBoost(b, pCtx); if (sb !== sa) return sb - sa; return (a.rank || 0) - (b.rank || 0); }); recs.forEach((r, i) => { r.rank = i + 1; }); const { rec: tryRec, reason: tryReason } = pickGrassrootsTryFirst(recs, pCtx); if (tryRec) { if (!tryRec.flags) tryRec.flags = {}; tryRec.flags.try_first = true; const idx = recs.indexOf(tryRec); if (idx > 0) { recs.splice(idx, 1); recs.unshift(tryRec); recs.forEach((r, i) => { r.rank = i + 1; }); } } const canWait = buildGrassrootsCanWait(recs, tryRec, pCtx); const prioritizationNote = buildGrassrootsPrioritizationNote(recs, tryRec, pCtx, tryReason, canWait); const actionRecommendations = recs.filter( (r) => (r.flags?.priority_tier ?? 99) <= PRIOR_TIER.FINE_TUNE ).slice(0, GRASSROOTS_ACTION_REC_CAP); const processRecommendations = recs.filter( (r) => (r.flags?.priority_tier ?? 99) > PRIOR_TIER.FINE_TUNE ); return { tryFirst: tryRec, tryFirstReason: tryReason, canWait, prioritizationNote, actionRecommendations, processRecommendations }; } function tryFirstRecTag(r) { if (!r) return "[TRY FIRST]"; if (r.basis === "finding") return `[TRY FIRST \xB7 ${r.confidence}]`; if (r.flags?.grassroots_handling) { const label = r.flags.symptom_label || "HANDLE"; return `[TRY FIRST \xB7 ${label}]`; } if (r.flags?.grassroots_first_move) return "[TRY FIRST \xB7 FIRST MOVE]"; if (r.flags?.grassroots_personalized) return "[TRY FIRST \xB7 YOUR CAR]"; if (r.flags?.grassroots_context_gap) return "[TRY FIRST \xB7 ADD CONTEXT]"; if (r.flags?.grassroots_tire) return "[TRY FIRST \xB7 TIRE]"; if (r.flags?.grassroots_surface) return "[TRY FIRST \xB7 SURFACE]"; if (r.flags?.grassroots_scaling) return "[TRY FIRST \xB7 SCALE]"; return "[TRY FIRST]"; } // scripts/lib/experimental/grassrootsEducationalExplanations.mjs var WHY = { psi_down_rs: { default: "Less air in the right-side tires lets the tread flex more into the dirt, so the front can hook and the car rotates easier into the corner.", push: "When the car pushes, softer right-side tires bite the track sooner instead of sliding straight.", entry: "On entry, a little less RF/RR pressure helps the nose turn before you get to the middle." }, psi_up_rs: { default: "More air in the right-side tires makes the tire stiffer \u2014 the car turns quicker but can feel free if you go too far.", loose: "If the car is loose, adding RS psi is usually the wrong direction \u2014 this note is for when you need less bite, not more.", exit: "Stiffer RR can unload faster off the corner \u2014 only use when you have confirmed too much rear grip." }, psi_down_ls: { default: "Lowering left-side psi lets the LR/LF grow taller as they heat up, which adds rear bite and can tighten the car off the corner.", loose: "Too much LR bite can make the rear step out \u2014 log hot stagger before dropping left-side again." }, psi_up_ls: { default: "More left-side air slows how much the LR grows, which loosens the car and lets it rotate faster in the middle.", entry: "Entry loose often means too much rear bite early \u2014 a small LS bump can calm the rear without a big geometry move." }, stagger_out: { default: "More rear stagger (LR taller than RR) adds bite on exit because the left rear digs in while the right rear rolls easier.", loose: "Exit loose means the rear is stepping out \u2014 stagger out gives the LR more grip to drive off the corner.", exit: "The LR is your drive tire on dirt \u2014 a little more stagger helps it push you forward instead of spinning." }, stagger_in: { default: "Less rear stagger frees the car in the middle because the RR and LR work more evenly \u2014 the rear rotates easier.", loose: "When the car is loose everywhere, tightening stagger is a basic way to calm the rear without stacking cross and wing.", mid: "Mid-corner free often means the rear is over-working \u2014 less stagger can settle it before you chase shocks." }, stagger_down: { default: "Less stagger frees the center of the corner \u2014 the rear rotates easier when LR and RR are closer in height.", push: "Center push sometimes means too much rear bite built in \u2014 less stagger lets the car turn in the middle." }, cross_down: { default: "Less cross weight on the right front lets the nose turn into the corner instead of plowing straight.", push: "Push means the front will not rotate \u2014 cross down moves weight off the RF so it can bite and steer.", entry: "On dirt, entry push is often too much cross or RF load \u2014 a small cross cut is a classic first fix." }, cross_up: { default: "More cross loads the right front harder, which can add bite but may tighten entry if you already have push.", loose: "Cross up can tighten a loose car by loading the front \u2014 use when the rear is clearly stepping out, not when entry already pushes." }, ride_height_up: { default: "Raising ride height adds mechanical grip by changing how weight transfers \u2014 the car rolls more and can bite better on slick dirt.", push: "A small ride-height bump can help the nose bite on entry when psi alone did not help \u2014 measure the same way every time." }, ride_height_down: { default: "Lowering ride height can free the car by reducing roll and unload \u2014 common loosen move on Hyper-style sheets.", loose: "Lower rear height can add exit drive, but too low can make the car snap loose \u2014 one corner at a time." }, weight_left: { default: "Moving weight left puts more load on the LR, which helps drive off the corner on dirt ovals.", push: "If cross is already high, check left-side weight before more RF psi \u2014 balance matters more than one magic number." }, weight_seat: { default: "Moving the seat changes where the driver weight sits on an offset kart \u2014 it is a big cross change in millimeters, not a small tweak.", push: "Seat forward can help the nose turn on outlaw karts; always log seat mm with cross so you can undo it." }, shock_comp_soft: { default: "Softer compression lets the tire reach the dirt faster on entry \u2014 the car settles instead of skipping across the bumps.", push: "Entry push shock trim: softening RF compression helps the front tire load before you turn the wheel hard." }, shock_comp_stiff: { default: "Stiffer compression holds the car up in the middle \u2014 it can tighten a loose car but may add push if the nose is already stiff.", loose: "Stiffening RR compression can calm a free rear in the middle \u2014 only after psi and stagger are logged stable." }, shock_reb_up: { default: "More rebound slows how fast weight comes off that corner \u2014 it can tighten exit but too much makes the car hop on rough dirt.", exit: "Exit loose sometimes needs more RR rebound to keep the tire planted \u2014 one click, then re-run." }, shock_reb_down: { default: "Less rebound lets weight leave the corner faster, which can free the car \u2014 common when the chassis feels bound up.", push: "If the car pushes because the front stays loaded too long, a little more LF rebound can help the nose rotate." }, wing_back: { default: "Less wing angle takes load off the rear on entry so the nose can turn \u2014 common on winged micro/lightning when the car pushes in.", push: "Wing back is an aero loosen move on entry \u2014 use when psi and RH are already on your baseline sheet." }, wing_forward: { default: "More wing loads the rear for bite on exit \u2014 it can tighten a loose car but adds push if entry already will not turn.", loose: "Wing forward adds rear downforce for drive \u2014 separate run from stagger so you know which helped." }, surface_drying: { default: "When the track dries, grip falls and the car pushes \u2014 less RS psi or stagger often comes before big geometry because the tire needs to flex again." }, surface_slick: { default: "On slick dirt the car slides more \u2014 protecting RR bite and using less cross keeps the rear driving instead of spinning." }, surface_tacky: { default: "On tacky dirt the tires bite hard \u2014 log hot psi after a few laps so your next move is based on what the tire did, not a cold-number guess." }, surface_greasy: { default: "Greasy tracks have moisture on top \u2014 a small RS psi change often works before big wing or cross moves because the tire needs to cut through the slime." }, tire_hot_log: { default: "Hot psi after laps 8\u201310 tells you what the tire actually did \u2014 cold numbers guess; hot numbers teach you the next 1 psi step." }, scale_baseline: { default: "Scaling shows where weight really sits \u2014 cross and left % explain push and loose better than guessing from feel alone." }, one_change: { default: "One change per run is how you learn cause and effect \u2014 if you change three things, you never know which one helped." }, shock_basics: { default: "Compression controls how fast weight loads into a corner; rebound controls how fast it unloads \u2014 shocks trim the platform after tires and ride height, they are not replacement springs." }, push_general: { default: "Push means the front slides instead of turning \u2014 on dirt, fix entry first with psi, cross, or ride height before loosening the rear." }, loose_general: { default: "Loose means the rear steps out \u2014 give the LR something to bite with stagger or calm RS before you stiffen the whole car." }, tight_in_loose_off: { default: "Tight in / loose off is two problems \u2014 fix entry bite first on one run, then exit rear bite on the next. Same-run stacks usually make both worse." }, wheel_hop: { default: "Wheel hop means the tire is skipping across the bumps instead of staying planted \u2014 softer compression and a stable platform fix it; stiffer shocks usually make hop worse on rough dirt.", push: "Hop often feels like push because the front skips \u2014 fix platform before chasing cross or stagger." }, rear_track: { default: "Rear track width changes how much the LR and RR share load \u2014 wider rear track can free the center; narrower can add bite on straight-rail flat karts.", push: "Mid push on flat karts sometimes means RS overloaded \u2014 rear track in can settle the middle." }, axle_slot: { default: "On outlaw floater karts, moving the axle forward in the slot helps rotation when grip falls; rearward adds bite. Each slot turn also shifts cross roughly 1%.", push: "Entry push on slick outlaw tracks often responds to axle forward before another cross washer \u2014 log cassette hole with every scale." }, flat_kart_cross: { default: "Flat karts run higher cross (~62\u201368% tacky) than winged outlaw karts because there is no wing loading the rear \u2014 cross and seat carry most entry balance on straight-rail dirt oval.", push: "Flat kart push is usually cross or seat first, not wing \u2014 outlaw wing levers do not apply." }, outlaw_cross_lower: { default: "Winged outlaw karts often run lower cross (~59\u201366%) than flat karts because the wing adds rear load \u2014 stacking high cross + high wing tightens entry fast on floaters.", loose: "Loose entry on outlaw karts may mean cross too high for tonight \u2014 cross down or wing back before adding stagger." }, qm_xmethod: { default: "Quarter midget X-method cross moves weight onto the RF for bite \u2014 small 0.5% steps because light cars feel every percent on offset panhard setups.", push: "QM entry push often responds to RF/RR psi or cross down before camber \u2014 Stanley/RSR sheets prioritize pressure first on tacky." }, micro_inch_stagger: { default: "600 micro sprints use inch rear stagger as a primary middle lever \u2014 trailing-arm cars (PMP, Stallard, D1) respond to stagger before bar turns; Hyper wishbone cars share psi/RH order but different geometry.", exit: "Exit loose on micro wants rear bite via stagger out or RR support \u2014 protect RR on slick before cross stacks." } }; var SYMPTOM_PHASE = { entry_push: "entry", mid_push: "mid", exit_loose: "exit", exit_tight: "exit", mid_loose: "mid", entry_loose: "entry", center_flat: "mid", wont_turn: "entry", washes_up: "entry", tight_in_loose_off: "tight_in_loose_off", wheel_hop: "all", everywhere_loose: "loose", everywhere_tight: "push" }; function detectGrassrootsAdjustmentTopic(r) { const text = `${r.action || ""} ${r.lever || ""} ${r.reason || ""}`.toLowerCase(); if (r.flags?.grassroots_surface || r.flags?.grassroots_tire) { if (/hot psi|laps 8|cold psi|cold numbers/.test(text)) return "tire_hot_log"; if (/effective stagger|log.*stagger|stagger after heat/.test(text)) return "stagger_out"; } if (r.flags?.grassroots_surface) { if (/drying|sheen broke|grip falls/.test(text)) return "surface_drying"; if (/slick|heavy/.test(text)) return "surface_slick"; if (/greasy|wet|moisture/.test(text)) return "surface_greasy"; return "surface_tacky"; } if (r.flags?.grassroots_scaling && /scale|cross|left %|weight target/.test(text)) return "scale_baseline"; if (r.lever === "one_change" || /one change per run|one lever/.test(text)) return "one_change"; if (/comp soft|soft comp|softer comp|soften.*comp/.test(text)) return "shock_comp_soft"; if (/comp stiff|stiff comp|stiffen.*comp/.test(text)) return "shock_comp_stiff"; if (/reb up|rebound up|reb up/.test(text)) return "shock_reb_up"; if (/reb down|rebound down|soft reb/.test(text)) return "shock_reb_down"; if (/wing back|wing down|less wing/.test(text)) return "wing_back"; if (/wing forward|wing up|more wing|wing angle up/.test(text)) return "wing_forward"; if (/cross down|less cross|cut cross/.test(text)) return "cross_down"; if (/cross up|more cross/.test(text)) return "cross_up"; if (/stagger out|stagger up|more stagger/.test(text)) return "stagger_out"; if (/stagger in|less stagger|stagger down/.test(text)) return "stagger_in"; if (/down.*stagger/.test(text)) return "stagger_down"; if (/lf\/lr psi up|lf.*lr.*up|ls psi up/.test(text)) return "psi_up_ls"; if (/lf\/lr psi down|lf.*lr.*down/.test(text)) return "psi_down_ls"; if (/rf\/rr psi down|rs psi down|ease rf|ease rr|psi down|down 1 psi|lower.*psi/.test(text)) return "psi_down_rs"; if (/rf\/rr psi up|rr psi up|rs psi up|psi up 1/.test(text)) return "psi_up_rs"; if (/ride height|rh \+|rh down|\+1\/16.*height|\+1 turn.*height|turns rear/.test(text)) { if (/logged stable|measure the same|before hot laps|same way every/.test(text)) { } else if (/lower|height down|down 1–3 turns rear|down 1-3 turns/.test(text)) { return "ride_height_down"; } else if (/\+1|raise|bump|tweak per|rh \+|turns rear/.test(text)) { return "ride_height_up"; } } if (/seat forward|seat back|seat mm/.test(text)) return "weight_seat"; if (/left %|ballast left|weight left|left-side/.test(text)) return "weight_left"; if (r.flags?.symptom_key === "tight_in_loose_off") return "tight_in_loose_off"; if (r.flags?.symptom_key === "wheel_hop" || /wheel hop|wheelhop|hopping/.test(text)) return "wheel_hop"; if (/axle slot|axle forward|cassette hole|floater/.test(text)) return "axle_slot"; if (/rear track/.test(text)) return "rear_track"; if (/x-method|x method/.test(text)) return "qm_xmethod"; if (/trailing-arm|trailing arm|pmp|gre6/.test(text)) return "micro_inch_stagger"; if (/flat kart|straight-rail|lo206/.test(text) && /cross/.test(text)) return "flat_kart_cross"; if (/outlaw|winged outlaw|floater/.test(text) && /cross down|less cross/.test(text)) return "outlaw_cross_lower"; if (/push|tight|understeer|plow|washes/.test(text)) return "push_general"; if (/loose|free|fishtail|oversteer/.test(text)) return "loose_general"; return null; } function grassrootsEducationalWhy(r, ctx = {}) { const symptomKey = r.flags?.symptom_key || ctx.diagnosedSymptom?.key; if (symptomKey === "tight_in_loose_off" && r.flags?.grassroots_handling && String(r.lever || "").startsWith("handle_fix_")) { return WHY.tight_in_loose_off.default; } if (symptomKey === "wheel_hop" && r.flags?.grassroots_handling) { return WHY.wheel_hop.default; } const topic = detectGrassrootsAdjustmentTopic(r); if (!topic) { if (r.flags?.grassroots_handling) { return profileClassWhy(ctx.profile, symptomKey); } return null; } const entry = WHY[topic]; if (!entry) return null; if (ctx.profile === "flat_kart" && topic === "cross_down") { return WHY.flat_kart_cross.push || WHY.flat_kart_cross.default; } if (ctx.profile === "outlaw_kart" && topic === "cross_down") { return WHY.outlaw_cross_lower.push || WHY.outlaw_cross_lower.default; } if (ctx.profile === "quarter_midget" && (topic === "cross_down" || topic === "cross_up")) { return WHY.qm_xmethod.push || WHY.qm_xmethod.default; } if (ctx.profile === "micro_sprint_600" && topic === "stagger_out") { return WHY.micro_inch_stagger.exit || WHY.micro_inch_stagger.default; } if (symptomKey && entry[symptomKey]) return entry[symptomKey]; const phase = symptomKey ? SYMPTOM_PHASE[symptomKey] : null; if (phase && entry[phase]) return entry[phase]; if (symptomKey && /push|tight|wont|wash|center_flat|exit_tight|everywhere_tight/.test(symptomKey) && entry.push) { return entry.push; } if (symptomKey && /loose|free|everywhere_loose/.test(symptomKey) && entry.loose) { return entry.loose; } return entry.default; } function profileClassWhy(profile2, symptomKey) { if (!profile2) return null; if (profile2 === "flat_kart" && /push|tight|wont|wash|center_flat|exit_tight|everywhere_tight/.test(symptomKey || "")) { return WHY.flat_kart_cross.default; } if (profile2 === "outlaw_kart" && /push|tight|entry|center_flat/.test(symptomKey || "")) { return WHY.outlaw_cross_lower.default; } if (profile2 === "quarter_midget" && /push|tight|cross/.test(symptomKey || "")) { return WHY.qm_xmethod.default; } if ((profile2 === "micro_sprint_600" || profile2 === "micro_sprint_600_trailing") && /loose|exit|stagger|mid/.test(symptomKey || "")) { return WHY.micro_inch_stagger.default; } if (symptomKey === "wheel_hop") return WHY.wheel_hop.default; return null; } function isGrassrootsEduTarget(r) { if (!r || r.basis === "finding") return true; const f = r.flags || {}; return Boolean( f.grassroots_handling || f.grassroots_tire || f.grassroots_surface || f.grassroots_scaling || f.grassroots_shock || f.grassroots_first_move || f.grassroots_personalized || f.grassroots_process || f.public_baseline || f.chassis_specific ); } function attachGrassrootsEducationalNotes(recs, ctx = {}) { if (!ctx.profile || !isGrassrootsLearningProfile(ctx.profile)) { return { nEducational: 0 }; } let nEducational = 0; for (const r of recs) { if (!isGrassrootsEduTarget(r)) continue; if (r.basis === "finding") { r.whyLearn = "This came from YOUR logged runs \u2014 that is the best teacher because it proves what worked on your car at this track."; if (!r.flags) r.flags = {}; r.flags.grassroots_edu = true; nEducational += 1; continue; } const whyLearn = grassrootsEducationalWhy(r, ctx) || r.flags?.grassroots_shock && WHY.shock_basics?.default || null; if (!whyLearn) continue; r.whyLearn = whyLearn; if (!r.flags) r.flags = {}; r.flags.grassroots_edu = true; nEducational += 1; } return { nEducational }; } // scripts/lib/experimental/setupRecommender.mjs var MAG = { panhard: "1 hole", comp: "2 clicks", reb: "2 clicks", bump: "toggle", helper: "1 step", stagger: "1/4 in", gear: "1 tooth", sipe: "add a few" }; var CONF_W = { HIGH: 3, MODERATE: 2, LOW: 1 }; var baseId2 = (s) => String(s.study).split(":")[0]; var roughState = (st) => ["slick", "dry_slick", "heavy"].includes(st); var RULES = { P1(e, ctx) { if (e.stratum && ctx.track_state && e.stratum !== ctx.track_state) return null; const helped = e.d < 0; return rec( helped ? "Raise the panhard/J-bar" : "Lower the panhard/J-bar", "panhard", e, `P1: on ${e.stratum || "this state"}, ${helped ? "raising" : "lowering"} the bar reduced grip loss`, ctx, { state_specific: true } ); }, P2(e, ctx) { if (!roughState(ctx.track_state) && ctx.track_state) return null; if (e.d > 0) return rec( "Don't raise the panhard chasing bite on a rough/slick track", "panhard", e, "P2: a higher bar added wheel hop on rough (cost stability)", ctx, { guard: true } ); return null; }, S1(e, ctx) { if (!roughState(ctx.track_state) && ctx.track_state) return null; if (e.d > 0) return rec( "Soften high-speed compression (don't add it) on the slick", "comp", e, "S1: stiffer HS compression added wheel hop", ctx, {} ); return null; }, S2(e, ctx) { if (e.d < 0) return rec( "If you're bottoming, add a little compression", "comp", e, "S2: more compression cut bottoming (but won't fix hop -- see S1)", ctx, { conditional: "only if bottoming" } ); return null; }, S3(e, ctx) { if (!roughState(ctx.track_state) && ctx.track_state) return null; if (e.d > 0) return rec( "Soften high-speed rebound to settle wheel hop", "reb", e, "S3: HS rebound drove wheel hop on rough", ctx, {} ); return null; }, S4(e, ctx) { if (e.d < 0) return rec( "Don't add high-speed rebound chasing drive off", "reb", e, "S4: more HS rebound hurt forward drive on rough", ctx, { guard: true } ); return null; }, B1(e, ctx) { if (!roughState(ctx.track_state) && ctx.track_state) return null; if (e.d > 0) return rec( "Back the bump stop out on the slick", "bump", e, "B1: engaging the bump added wheel hop on rough", ctx, {} ); return null; }, H1(e, ctx) { if (!roughState(ctx.track_state) && ctx.track_state) return null; if (e.d < 0) return rec( "Go a step softer on the helper springs", "helper", e, "H1: softer helpers cut wheel hop on rough", ctx, {} ); return null; }, G1(e, ctx) { if (e.d > 0) return rec( "If the tires come in cold, add a few sipes", "sipe", e, "G1: siping sped tire heat-up on cold/hard tires", ctx, { conditional: "cold start only" } ); return null; }, G2(e, ctx) { if (e.stratum && ctx.track_state && e.stratum !== ctx.track_state) return null; const helped = e.d < 0; return rec( helped ? "Groove the tire for this surface" : "Run less/no grooving here", "sipe", e, `G2: on ${e.stratum || "this state"}, ${helped ? "grooving reduced" : "grooving increased"} grip loss`, ctx, { state_specific: true } ); } }; function rec(action, lever, entry, why, ctx, flags) { const conf = entry.level; const mag2 = lever === "bump" ? "" : MAG[lever] || ""; let reason = why; if (ctx.track_state) reason += ` + track is ${ctx.track_state}`; if (ctx._dryNote) reason += `, ${ctx._dryNote}`; const caveat = conf === "MODERATE" ? "Moderate confidence (didn't survive multi-test correction) -- confirm with a clean A-B-A." : ""; return { action: action + (mag2 ? ` (${mag2})` : ""), lever, basis: "finding", confidence: conf, refs: [entry.study], reason, caveat, flags: { user_data: true, ...flags || {} } }; } var PRIORS = [ { when: "slicking", lever: "stagger", action: "Come down on stagger (1/4 in)", reason: "prior: as the track slicks, free the center first (the bank already rotates the car)" }, { when: "slicking", lever: "rear_bite", action: "Pull a little rear/LR bite", reason: "prior: less rear bite keeps it from getting snappy-free on the slick" }, { when: "slicking", lever: "rr", action: "Protect the right rear (soften / slight RR)", reason: "prior: keep the RR planted for drive off the corner" }, { when: "slicking", lever: "gear", action: "Gear up a notch", reason: "prior: carry momentum through the slick; the bank holds the speed" } ]; function injectPublicBaselines(recs, leversCovered, ctx, tc, mode) { if (mode === "none") return null; const baseline = resolvePublicBaseline(ctx.track_state, tc, { skipLevers: leversCovered }); const thin = mode === "thin"; const advancedLayer = isAdvancedLearningProfile(baseline.profile); const scaffoldSuffix = advancedLayer ? ADVANCED_SCAFFOLD_CAVEAT_SUFFIX : GRASSROOTS_SCAFFOLD_CAVEAT_SUFFIX; const scaffoldCap = advancedLayer ? ADVANCED_SCAFFOLD_CAP : GRASSROOTS_SCAFFOLD_CAP; let scaffoldCount = 0; const mkCaveat = (base) => thin && base ? base + scaffoldSuffix : base; const canScaffold = () => !thin || scaffoldCount < scaffoldCap; const push = (p, refs, flags, caveat) => { if (leversCovered.has(p.lever) || !canScaffold()) return; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs, reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: mkCaveat(caveat), flags: thin ? { ...flags, scaffold: true } : flags }); leversCovered.add(p.lever); if (thin) scaffoldCount += 1; }; const enriched = baseline.vehicle_context || {}; for (const p of baseline.chassisPriors || []) { const chassisCaveat = p.caveat || (typeof baseline.chassisDeepCaveat === "string" ? baseline.chassisDeepCaveat : null) || baseline.caveat; push(p, ["public_baseline", "chassis_routing"], { chassis_specific: true, advanced_chassis: Boolean( baseline.advanced_chassis_layer || baseline.advanced_geometry_layer || enriched.advanced_chassis_layer ), advanced_geometry: /^advanced_geo_/.test(String(p.lever || "")), thin_data_chassis: /^thin_data_|^advanced_db_/.test(String(p.lever || "")), source: baseline.source, profile: baseline.profile, chassis_key: enriched.chassis_profile_key || enriched.chassis_mfr_key || null, chassis_mfr: enriched.chassis_manufacturer || null, chassis_model: enriched.chassis_model || null }, chassisCaveat); } for (const p of baseline.priors) { push(p, ["public_baseline"], { public_baseline: true, source: baseline.source, profile: baseline.profile }, baseline.caveat); } for (const p of baseline.surfacePriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_surface"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.surfaceCaveat || p.reason, flags: { grassroots_surface: true, surface_bucket: bucket, source: baseline.surfaceSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.tirePriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_tire"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.tireCaveat || p.reason, flags: { grassroots_tire: true, surface_bucket: bucket, source: baseline.tireSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.scalingPriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_scaling"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.scalingCaveat || p.reason, flags: { grassroots_scaling: true, surface_bucket: bucket, source: baseline.scalingSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.handlingPriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_handling"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.handlingCaveat || p.reason, flags: { grassroots_handling: true, symptom_key: baseline.diagnosedSymptom?.key || null, symptom_label: baseline.diagnosedSymptom?.label || "HANDLE", surface_bucket: bucket, source: baseline.handlingSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.shockPriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_shock"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.shockCaveat || p.reason, flags: { grassroots_shock: true, surface_bucket: bucket, source: baseline.shockSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.practicePriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_practice"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.practiceCaveat || p.reason, flags: { grassroots_practice: true, surface_bucket: bucket, source: baseline.practiceSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.racePriors || []) { if (leversCovered.has(p.lever)) continue; const bucket = baseline.surfaceBucket || "unknown"; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_race"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.raceCaveat || p.reason, flags: { grassroots_race: true, surface_bucket: bucket, source: baseline.raceSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.advancedLoggingPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["advanced_logging"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.advancedLoggingCaveat || p.caveat || p.reason, flags: { advanced_logging: true, advanced_chassis: true, source: baseline.advancedLoggingSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.advancedBetweenSessionPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["advanced_between_session"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.caveat || p.caveat || p.reason, flags: { advanced_chassis: true, advanced_geometry: true, advanced_between_session: true, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.advancedTrackEvolutionPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["advanced_track_evolution"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.caveat || p.caveat || p.reason, flags: { advanced_chassis: true, advanced_track_evolution: true, advanced_forward_plan: p.lever === "advanced_evo_forward_plan", profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.lateModelTrackEvolutionPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["late_model_track_evolution"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.caveat || p.caveat || p.reason, flags: { late_model_evolution: true, advanced_forward_plan: p.lever === "lm_evo_forward_plan", track_evolution_conflict: p.lever === "lm_evo_conflict", profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.multiShockDamperPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["multi_shock_damper"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.caveat || p.reason, flags: { advanced_chassis: true, multi_shock_damper: true, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.lmModModernPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: p.flags?.user_data ? "MODERATE" : "LOW", refs: ["lm_mod_modern", "late_model_pivot"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.caveat || p.reason, flags: { advanced_chassis: true, lm_mod_modern: true, advanced_geometry: p.lever?.includes("shock") || p.lever?.includes("pivot"), builder_signal: p.flags?.builder_signal, scaffold: p.flags?.scaffold, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.loggingPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_logging"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.loggingCaveat || p.reason, flags: { grassroots_logging: true, source: baseline.loggingSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.trackSnapPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["track_snap"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: "Optional track report \u2014 logged runs override when you have more session data", flags: { track_snap: true, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.builderSignalPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["builder_signal"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: "Builder-specific signal from your logged setup \u2014 confirm with A-B-A on your car.", flags: { builder_signal: true, builder_mfr: p.builder_mfr, builder_key: p.builder_key, chassis_specific: true, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.abaDisciplinePriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["aba_discipline"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: "Testing discipline \u2014 your logged A-B-A overrides every prior below.", flags: { aba_discipline: true, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.contextPriors || []) { if (leversCovered.has(p.lever)) continue; const advancedCtx = Boolean(baseline.advanced_context_layer); recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: [advancedCtx ? "advanced_personalized" : "grassroots_personalized"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.contextPersonalizedCaveat || p.reason, flags: advancedCtx ? { advanced_context: true, source: baseline.profile, profile: baseline.profile } : { grassroots_personalized: true, source: baseline.profile, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.firstMovePriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_first_move"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.guardrailsCaveat || p.reason, flags: { grassroots_first_move: true, surface_bucket: baseline.surfaceBucket || "unknown", source: baseline.firstMoveSource, profile: baseline.profile } }); leversCovered.add(p.lever); } for (const p of baseline.guardrailPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_guardrail"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.guardrailsCaveat || p.reason, flags: { grassroots_guardrail: true, surface_bucket: baseline.surfaceBucket || "unknown", source: baseline.guardrailSource, profile: baseline.profile } }); leversCovered.add(p.lever); } if (!thin) { for (const p of baseline.contextGapPriors || []) { if (leversCovered.has(p.lever)) continue; const advancedCtx = Boolean(baseline.advanced_context_layer); recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: [advancedCtx ? "advanced_context_gap" : "grassroots_context_gap"], reason: p.reason, caveat: baseline.contextGapCaveat || p.reason, flags: advancedCtx ? { advanced_context_gap: true, profile: baseline.profile } : { grassroots_context_gap: true, profile: baseline.profile } }); leversCovered.add(p.lever); } } if (!thin) { for (const p of baseline.commonPriors || []) { push(p, ["grassroots_common"], { grassroots_common: true, source: baseline.commonSource, profile: baseline.profile }, baseline.commonCaveat || p.reason); } for (const p of baseline.dayOnePriors || []) { push(p, ["grassroots_process"], { grassroots_process: true, source: baseline.dayOneSource, profile: baseline.profile }, baseline.dayOneCaveat || p.reason); } } return baseline; } function recWeight(r) { return CONF_W[r.confidence] + (r.basis === "finding" ? 0.55 : 0) + (r.flags?.user_data && r.basis === "finding" ? 0.05 : 0) - (r.flags?.guard || r.flags?.conditional ? 0.3 : 0) - (r.flags?.event_prior ? 0.1 : 0) - (r.flags?.grassroots_aba ? 0.04 : 0) - (r.flags?.chassis_specific ? 0.02 : 0) - (r.flags?.grassroots_handling ? 0.019 : 0) - (r.flags?.grassroots_personalized ? 0.025 : 0) - (r.flags?.grassroots_surface ? 0.03 : 0) - (r.flags?.grassroots_tire ? 0.032 : 0) - (r.flags?.grassroots_scaling ? 0.034 : 0) - (r.flags?.grassroots_shock ? 0.078 : 0) - (r.flags?.grassroots_race ? 0.084 : 0) - (r.flags?.grassroots_logging ? 0.0845 : 0) - (r.flags?.grassroots_practice ? 0.085 : 0) - (r.flags?.grassroots_first_move ? 0.065 : 0) - (r.flags?.grassroots_guardrail ? 0.075 : 0) - (r.flags?.public_baseline ? 0.05 : 0) - (r.flags?.grassroots_process ? 0.09 : 0) - (r.flags?.grassroots_common ? 0.12 : 0) - (r.flags?.grassroots_context_gap ? 0.14 : 0) - (r.flags?.scaffold ? 0.35 : 0) - (r.flags?.advanced_track_evolution ? 0.028 : 0) - (r.flags?.track_snap || r.flags?.advanced_track_snap ? 0.018 : 0) - (r.flags?.late_model_evolution ? 0.026 : 0) - (r.flags?.builder_signal ? 0.032 : 0) - (r.flags?.aba_discipline ? 0.034 : 0); } function recommend(reportOrBundle, opts = {}) { const report = reportOrBundle.report || reportOrBundle; const tc = report.track_condition || {}; const ctx = { track_state: tc.track_state || tc.state || opts.track_state || null }; const dry = tc.drying_prediction || tc.drying; if (dry) ctx._dryNote = "surface drying (" + (typeof dry === "string" ? dry : dry.rate || "see track intel") + ")"; const nullBad = report.null_check && report.null_check.status === "WARNING"; const bc39Mode = isEventModeBc39(tc); const profile2 = !bc39Mode ? resolvePublicBaselineProfile(tc) : null; const grassrootsLearning = profile2 && isGrassrootsLearningProfile(profile2); const advancedLearning = profile2 && isAdvancedLearningProfile(profile2); const recs = []; const leversCovered = /* @__PURE__ */ new Set(); if (!nullBad) { for (const e of report.studies || []) { if (!(e.level === "HIGH" || e.level === "MODERATE") || e.verdict !== "SIGNAL" || e.d == null) continue; const rule = RULES[baseId2(e)]; const r = rule ? rule(e, ctx) : genericFindingRec(e, ctx); if (r) { if (!r.flags) r.flags = { user_data: true }; else if (!r.flags.user_data) r.flags.user_data = true; recs.push(r); leversCovered.add(r.lever); } } } const nFind = recs.filter((r) => r.basis === "finding").length; const hasFindings = nFind > 0; const hasHigh = recs.some((r) => r.basis === "finding" && r.confidence === "HIGH"); const baselineMode = advancedLearning ? resolveAdvancedBaselineInjectionMode(profile2, { nFind, bc39Mode }) : resolveBaselineInjectionMode(profile2, { nFind, hasHigh, bc39Mode }); const slicking = ctx.track_state ? roughState(ctx.track_state) : true; const skipGenericSlickPriors = grassrootsLearning && hasFindings; if (slicking && !skipGenericSlickPriors) { for (const p of PRIORS) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: [], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: "Based on priors, no data for this lever yet -- confirm with an A-B-A.", flags: {} }); leversCovered.add(p.lever); } } const eventPriors = tc.event_context?.recommendation_priorities || tc.recommendation_priorities || []; if (!hasFindings && eventPriors.length) { eventPriors.forEach((p, i) => { const lever = p.lever || `event_${i}`; if (leversCovered.has(lever)) return; recs.push({ action: p.text, lever, basis: "prior", confidence: "LOW", refs: [], reason: `event prior${tc.event_context?.event ? ` (${tc.event_context.event})` : ""}: ${p.text}`, caveat: "Not enough experimental data yet -- confirm with an A-B-A before booking.", flags: { event_prior: true } }); leversCovered.add(lever); }); } let contextAssessment = null; let diagnosedSymptom = null; let resolvedBaseline = null; if (!bc39Mode && baselineMode !== "none") { const baselineBlock = injectPublicBaselines(recs, leversCovered, ctx, tc, baselineMode); resolvedBaseline = baselineBlock; contextAssessment = baselineBlock?.contextAssessment || null; diagnosedSymptom = baselineBlock?.diagnosedSymptom || null; } const topFinding = report.key_findings && report.key_findings[0] || null; const topLevel = topFinding?.level || null; if (grassrootsLearning && !bc39Mode && !nullBad) { const aba = grassrootsAbaNudgeRec(hasFindings, topFinding); if (!leversCovered.has(aba.lever)) { recs.push(aba); leversCovered.add(aba.lever); } } if (!bc39Mode && hasFindings) { for (const p of abaTestingDisciplinePriors({ hasFindings: true, topLevel, nFind, lateNight: false })) { if (leversCovered.has(p.lever)) continue; if (p.lever === "aba_no_findings" || p.lever === "aba_build_baseline") continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["aba_discipline"], reason: p.reason, caveat: "Confirm on same track state before booking another lever.", flags: { aba_discipline: true, user_data: true } }); leversCovered.add(p.lever); } const advAba = advancedAbaInterpretationPrior(hasFindings, topFinding); if (advAba && !leversCovered.has(advAba.lever) && (advancedLearning || isLateModelEvolutionProfile(profile2))) { recs.push({ ...advAba, basis: "prior", confidence: topLevel === "HIGH" ? "MODERATE" : "LOW", refs: ["aba_discipline", "advanced_aba"], flags: { aba_discipline: true, advanced_chassis: true, user_data: true } }); leversCovered.add(advAba.lever); } } recs.sort((a, b) => recWeight(b) - recWeight(a)); recs.forEach((r, i) => r.rank = i + 1); let tryFirst = null; let tryFirstReason = null; let tryFirstTier = null; let canWait = []; let prioritizationNote = null; let dataQualityNote = null; let recommendationGroups = null; let actionRecommendations = recs; let processRecommendations = []; if (grassrootsLearning && !bc39Mode) { const priorBlock = applyGrassrootsPrioritization(recs, { profile: profile2, nFind, hasFindings, baselineMode, contextAssessment, diagnosedSymptom }, recWeight); tryFirst = priorBlock.tryFirst; tryFirstReason = priorBlock.tryFirstReason; canWait = priorBlock.canWait; prioritizationNote = priorBlock.prioritizationNote; actionRecommendations = priorBlock.actionRecommendations; processRecommendations = priorBlock.processRecommendations; } else if ((advancedLearning || isLateModelEvolutionProfile(profile2)) && !bc39Mode) { const priorBlock = applyAdvancedPrioritization(recs, { profile: profile2, nFind, hasFindings, contextAssessment, vehicleContext: tc.vehicle_context || {}, sessionLog: tc.session_log || [], trackState: ctx.track_state }, recWeight); tryFirst = priorBlock.tryFirst; tryFirstReason = priorBlock.tryFirstReason; tryFirstTier = priorBlock.tryFirstTier; canWait = priorBlock.canWait; prioritizationNote = priorBlock.prioritizationNote; dataQualityNote = priorBlock.dataQualityNote; recommendationGroups = priorBlock.recommendationGroups; actionRecommendations = priorBlock.actionRecommendations; processRecommendations = priorBlock.processRecommendations; if (priorBlock.diagnosedSymptom) diagnosedSymptom = priorBlock.diagnosedSymptom; } let nEducational = 0; if (grassrootsLearning && !bc39Mode) { ({ nEducational } = attachGrassrootsEducationalNotes(recs, { profile: profile2, diagnosedSymptom })); } const nPub = recs.filter((r) => r.flags && r.flags.public_baseline).length; const nCommon = recs.filter((r) => r.flags && r.flags.grassroots_common).length; const nProcess = recs.filter((r) => r.flags && r.flags.grassroots_process).length; const nSurface = recs.filter((r) => r.flags && r.flags.grassroots_surface).length; const nTire = recs.filter((r) => r.flags && r.flags.grassroots_tire).length; const nScaling = recs.filter((r) => r.flags && r.flags.grassroots_scaling).length; const nHandling = recs.filter((r) => r.flags && r.flags.grassroots_handling).length; const nShock = recs.filter((r) => r.flags && r.flags.grassroots_shock).length; const nPractice = recs.filter((r) => r.flags && r.flags.grassroots_practice).length; const nRace = recs.filter((r) => r.flags && r.flags.grassroots_race).length; const nLogging = recs.filter((r) => r.flags && r.flags.grassroots_logging).length; const nFirstMove = recs.filter((r) => r.flags && r.flags.grassroots_first_move).length; const nGuardrail = recs.filter((r) => r.flags && r.flags.grassroots_guardrail).length; const nScaffold = recs.filter((r) => r.flags && r.flags.scaffold).length; const mode = nFind ? recs.some((r) => r.basis === "prior" && !r.flags?.grassroots_aba) ? "mixed" : "findings" : "priors"; let dataNote = null; if (nullBad) dataNote = "Null control flagged noise -- fix logging before trusting setup moves."; else if (nFind && grassrootsLearning && nHandling) { dataNote = `${nFind} logged finding(s) on your car \u2014 ${GRASSROOTS_DATA_LEADS_NOTE} Handling suggestions from your logged feel still apply as secondary context.`; if (nScaffold) dataNote += ` (${nScaffold} secondary baseline hint${nScaffold > 1 ? "s" : ""} for uncovered levers.)`; } else if (nFind && grassrootsLearning && nSurface) { dataNote = `${nFind} logged finding(s) on your car \u2014 ${GRASSROOTS_DATA_LEADS_NOTE} Surface adjustments for ${ctx.track_state || "this condition"} still apply as secondary context.`; if (nScaffold) dataNote += ` (${nScaffold} secondary baseline hint${nScaffold > 1 ? "s" : ""} for uncovered levers.)`; } else if (nFind && grassrootsLearning) { dataNote = `${nFind} logged finding(s) on your car \u2014 ${GRASSROOTS_DATA_LEADS_NOTE}`; if (nScaffold) dataNote += ` (${nScaffold} secondary baseline hint${nScaffold > 1 ? "s" : ""} for uncovered levers.)`; } else if (nFind && advancedLearning) { const nGeo = recs.filter((r) => r.flags?.advanced_geometry).length; const nBuilder = recs.filter((r) => r.flags?.advanced_chassis && !r.flags?.advanced_geometry && !/^thin_data_/.test(String(r.lever || ""))).length; const chassisLabel = recs.find((r) => r.flags?.chassis_mfr)?.flags?.chassis_mfr || null; const hasLoggedFeel = recs.some((r) => r.flags?.grassroots_handling) || Boolean((tc.vehicle_context || {}).trait_entry); const sessionLog = tc.session_log || []; const scaleRowCount = sessionLog.filter((r) => r && (r.rf_psi != null || r.stagger != null)).length; dataNote = buildAdvancedDataNote({ nFind, nScaffold, nGeo, nBuilder, chassisLabel, hasLoggedFeel, sessionLogCount: sessionLog.length, scaleRowCount }); } else if (nFind && !grassrootsLearning && !advancedLearning) { dataNote = `${nFind} data-backed finding(s) drive recommendations \u2014 confirm each with A-B-A before booking.`; } else if (!nFind && (nPub || nCommon || nProcess || nSurface || nTire || nScaling || nShock || nPractice || nRace || nLogging || nFirstMove || nGuardrail)) { const commonSuffix = nCommon || nProcess ? " Includes cross-class grassroots principles and day-one process guidance." : ""; const surfaceSuffix = nSurface ? " Includes condition-based surface adjustments for tonight's track state." : ""; const tireSuffix = nTire ? " Includes tire pressure starting bands and management guidance for your class." : ""; const scalingSuffix = nScaling ? " Includes scaling and weight distribution guidance for your class." : ""; const shockSuffix = nShock ? " Includes basic shock philosophy and fine-tune guidance for your class." : ""; const practiceSuffix = nPractice ? " Includes structured practice-night strategy for your class." : ""; const raceSuffix = nRace ? " Includes race-night strategy for heats, feature, and calm decision-making." : ""; const loggingSuffix = nLogging ? " Includes setup logging and notebook habits so A-B-A and Crew Chief can learn from your runs." : ""; const guardSuffix = nFirstMove || nGuardrail ? " Includes smart first-move suggestions and common-mistake guardrails for newer handlers." : ""; const maximPub = recs.some((r) => r.flags?.profile === "maxim_sprint_winged"); const midgetAdv = recs.some((r) => r.flags?.profile === "hyper_midget"); const outlawPub = recs.some((r) => r.flags?.profile === "outlaw_kart"); const qmPub = recs.some((r) => r.flags?.profile === "quarter_midget"); const microPub = recs.some((r) => r.flags?.profile === "micro_sprint_600"); const lightningPub = recs.some((r) => r.flags?.profile === "lightning_sprint"); const flatPub = recs.some((r) => r.flags?.profile === "flat_kart"); const advancedChassis = recs.some((r) => r.flags?.advanced_chassis); const nAdvGeo = recs.filter((r) => r.flags?.advanced_geometry).length; const nAdvBuilder = recs.filter((r) => r.flags?.advanced_chassis && !r.flags?.advanced_geometry && !/^thin_data_/.test(String(r.lever || ""))).length; const advChassisLabel = recs.find((r) => r.flags?.chassis_mfr)?.flags?.chassis_mfr || null; if (outlawPub) dataNote = "Not enough experimental data yet -- Outlaw Kart public baseline (validate with logged runs)." + surfaceSuffix + tireSuffix + scalingSuffix + shockSuffix + practiceSuffix + raceSuffix + loggingSuffix + guardSuffix + commonSuffix; else if (flatPub) dataNote = "Not enough experimental data yet -- Flat Kart dirt oval public baseline (validate with logged A-B-A runs)." + surfaceSuffix + tireSuffix + scalingSuffix + shockSuffix + practiceSuffix + raceSuffix + loggingSuffix + guardSuffix + commonSuffix; else if (qmPub) dataNote = "Not enough experimental data yet -- Quarter Midget public baseline (validate with your chassis manufacturer's setup sheet)." + surfaceSuffix + tireSuffix + scalingSuffix + shockSuffix + practiceSuffix + raceSuffix + loggingSuffix + guardSuffix + commonSuffix; else if (microPub) dataNote = "Not enough experimental data yet -- Micro Sprint / 600cc public baseline (primarily Hyper Racing \u2014 validate with logged runs)." + surfaceSuffix + tireSuffix + scalingSuffix + shockSuffix + practiceSuffix + raceSuffix + loggingSuffix + guardSuffix + commonSuffix; else if (lightningPub) dataNote = "Not enough experimental data yet -- Lightning Sprint public baseline (primarily Hyper Racing \u2014 validate with logged runs)." + surfaceSuffix + tireSuffix + scalingSuffix + shockSuffix + practiceSuffix + raceSuffix + loggingSuffix + guardSuffix + commonSuffix; else if ((maximPub || midgetAdv) && advancedChassis) { const sessionLog = tc.session_log || []; const scaleRowCount = sessionLog.filter((r) => r && (r.rf_psi != null || r.stagger != null)).length; dataNote = buildAdvancedDataNote({ nGeo: nAdvGeo, nBuilder: nAdvBuilder, chassisLabel: advChassisLabel, sessionLogCount: sessionLog.length, scaleRowCount }); } else if (maximPub) dataNote = "Not enough experimental data yet -- Maxim Racing public baseline (validate with logged runs)."; else if (nCommon) dataNote = "Not enough experimental data yet -- grassroots dirt oval common principles (validate with your chassis and A-B-A)." + commonSuffix; else dataNote = "Not enough experimental data yet -- public baseline from manufacturer guidance (validate with A-B-A)." + commonSuffix; } else if (!nFind && recs.length) dataNote = "Not enough experimental data yet -- using track priors only."; else if (!nFind && !recs.length) dataNote = "Need track state and/or linked experiment runs for recommendations."; if (prioritizationNote) { dataNote = dataNote ? `${dataNote} ${prioritizationNote}` : prioritizationNote; } if (dataQualityNote?.headline && advancedLearning && !nFind) { dataNote = dataNote ? `${dataNote} ${dataQualityNote.headline}` : dataQualityNote.headline; } let multiSessionPlanning = null; let nightSummary = null; const evoCtx = resolvedBaseline?.evoCtx || buildEnrichedEvolutionContext(tc, ctx.track_state); if (!bc39Mode && (tc.session_log?.length || tc.track_snaps?.length)) { nightSummary = buildNightSummary(tc, ctx.track_state, profile2); } if (advancedLearning && !bc39Mode && isAdvancedTrackEvolutionProfile(profile2)) { multiSessionPlanning = buildMultiSessionPlanning( tc, tc.vehicle_context || {}, ctx.track_state, { profile: profile2, evoCtx } ); if (multiSessionPlanning.lines?.length) { const planNote = multiSessionPlanning.lines[0]; dataNote = dataNote ? `${dataNote} Plan ahead: ${planNote}` : `Plan ahead: ${planNote}`; } } else if (!bc39Mode && isLateModelEvolutionProfile(profile2)) { multiSessionPlanning = buildLateModelMultiSessionPlanning( tc, tc.vehicle_context || {}, ctx.track_state, { profile: profile2, evoCtx } ); if (multiSessionPlanning.lines?.length) { const planNote = multiSessionPlanning.lines[0]; dataNote = dataNote ? `${dataNote} Plan ahead: ${planNote}` : `Plan ahead: ${planNote}`; } } return { recommendations: recs, actionRecommendations, processRecommendations, tryFirst, tryFirstReason, tryFirstTier, canWait, prioritizationNote, dataQualityNote, recommendationGroups, nEducational, multiSessionPlanning, nightSummary, evolutionContext: evoCtx?.analysis ? { trend: evoCtx.analysis.trend, trendSource: evoCtx.analysis.trendSource, conflict: evoCtx.analysis.conflict, passiveSignals: evoCtx.passive?.signals || [] } : null, context: ctx, mode, dataNote, contextAssessment, diagnosedSymptom, summary: `${nFind} data-backed + ${recs.filter((r) => r.basis === "prior").length} prior-based; track_state=${ctx.track_state || "unknown"}` }; } function recTag(r) { if (r.flags?.try_first) { if (r.flags?.advanced_try_first || r.flags?.advanced_chassis) return advancedTryFirstRecTag(r); return tryFirstRecTag(r); } const advTag = advancedRecTag(r); if (advTag) return advTag; if (r.basis === "finding") return `[${r.confidence}]`; if (r.flags?.grassroots_handling) { const label = r.flags.symptom_label || "HANDLE"; return `[HANDLE \xB7 ${label}]`; } if (r.flags?.grassroots_surface) { const b = r.flags.surface_bucket ? surfaceBucketLabel(r.flags.surface_bucket) : "SURFACE"; return `[SURFACE \xB7 ${b}]`; } if (r.flags?.grassroots_tire) { const b = r.flags.surface_bucket ? surfaceBucketLabel(r.flags.surface_bucket) : "TIRE"; return `[TIRE \xB7 ${b}]`; } if (r.flags?.grassroots_scaling) { const b = r.flags.surface_bucket ? surfaceBucketLabel(r.flags.surface_bucket) : "SCALE"; return `[SCALE \xB7 ${b}]`; } if (r.flags?.grassroots_shock) { const b = r.flags.surface_bucket ? surfaceBucketLabel(r.flags.surface_bucket) : "SHOCK"; return `[SHOCK \xB7 ${b}]`; } if (r.flags?.grassroots_practice) { return "[PRACTICE NIGHT]"; } if (r.flags?.grassroots_race) { return "[RACE NIGHT]"; } if (r.flags?.grassroots_logging) { return "[LOG \xB7 HABITS]"; } if (r.flags?.grassroots_personalized) return "[YOUR CAR]"; if (r.flags?.grassroots_context_gap) return "[ADD CONTEXT]"; if (r.flags?.grassroots_first_move) return "[FIRST MOVE]"; if (r.flags?.grassroots_guardrail) return "[AVOID]"; if (r.flags?.track_snap || r.flags?.advanced_track_snap) return "[TRACK SNAP]"; if (r.flags?.builder_signal) return `[BUILDER \xB7 ${(r.flags.builder_mfr || "SIGNAL").toUpperCase()}]`; if (r.flags?.aba_discipline) return r.lever?.startsWith("aba_interpret") ? "[A-B-A \xB7 READ]" : "[A-B-A]"; if (r.flags?.late_model_evolution) { return r.flags?.track_evolution_conflict ? "[CONFLICT]" : r.flags?.advanced_forward_plan ? "[PLAN AHEAD \xB7 LM]" : "[EVOLUTION \xB7 LM]"; } if (r.flags?.advanced_track_evolution) { return r.flags?.advanced_forward_plan ? "[PLAN AHEAD]" : "[EVOLUTION \xB7 ADV]"; } if (r.flags?.advanced_logging) return r.flags?.scaffold ? "[REF \xB7 LOG]" : "[LOG \xB7 ADV]"; if (r.flags?.chassis_specific) { if (r.flags?.thin_data_chassis) return r.flags.scaffold ? "[SCAFFOLD \xB7 THIN DATA]" : "[THIN DATA]"; if (r.flags?.advanced_chassis) return r.flags.scaffold ? "[SCAFFOLD \xB7 ADV CHASSIS]" : "[ADV CHASSIS]"; return r.flags.scaffold ? "[SCAFFOLD \xB7 CHASSIS]" : "[CHASSIS BASELINE]"; } if (r.flags?.public_baseline) return r.flags.scaffold ? "[SCAFFOLD \xB7 BASELINE]" : "[PUBLIC BASELINE]"; if (r.flags?.grassroots_aba) return "[A-B-A]"; if (r.flags?.grassroots_process) return "[DAY-ONE GUIDE]"; if (r.flags?.grassroots_common) return "[GRASSROOTS COMMON]"; return "[PRIOR]"; } function renderRecommendations(out, title = "Setup Recommendations") { const L2 = []; const P = (s) => L2.push(s); P("=" + "=".repeat(58)); P(`SETUP RECOMMENDATIONS -- ${title}`); P("=" + "=".repeat(58)); P(`mode: ${out.mode} | ${out.summary}`); P(""); if (!out.recommendations.length) { P(" (no recommendations -- need track state and/or findings)"); return L2.join("\n"); } for (const r of out.recommendations) { P(` ${r.rank}. ${recTag(r)} ${r.action}`); P(` why: ${r.reason}${r.refs.length ? " (" + r.refs.join(",") + ")" : ""}`); if (r.whyLearn) P(` learn: ${r.whyLearn}`); if (r.flags && r.flags.conditional) P(` only: ${r.flags.conditional}`); if (r.caveat) P(` note: ${r.caveat}`); } P(""); P("Discipline: change ONE lever at a time, then re-run an A-B-A to confirm."); return L2.join("\n"); } // scripts/lib/experimental/advancedSmartDefaults.mjs var CAPTURE_OVERRIDE_STORE_KEY = "bb_capture_overrides_v1"; var STATE_BUCKETS = { tacky: ["tacky", "rubbered", "heavy"], drying: ["drying"], slick: ["dry_slick", "slick"], greasy: ["greasy", "wet"] }; function num5(v) { const n = Number(v); return Number.isFinite(n) ? n : null; } function round1(v) { return Math.round(v * 10) / 10; } function round22(v) { return Math.round(v * 100) / 100; } function normalizeTrackStateBucket(trackState) { const s = String(trackState || "").toLowerCase(); if (STATE_BUCKETS.slick.some((x) => s.includes(x) || s === x)) return "slick"; if (STATE_BUCKETS.drying.some((x) => s.includes(x) || s === x)) return "drying"; if (STATE_BUCKETS.greasy.some((x) => s.includes(x) || s === x)) return "greasy"; if (STATE_BUCKETS.tacky.some((x) => s.includes(x) || s === x)) return "tacky"; return "unknown"; } function bucketLabel(bucket) { const m = { tacky: "tacky", drying: "drying", slick: "dry-slick", greasy: "heavy/greasy", unknown: "this state" }; return m[bucket] || bucket; } function rowBucket(row) { return normalizeTrackStateBucket(row?.track_state); } function similarBucket(a, b) { if (a === b) return true; if (a === "tacky" && b === "drying" || a === "drying" && b === "tacky") return true; if (a === "drying" && b === "slick" || a === "slick" && b === "drying") return true; return false; } function fieldKey(row, key) { if (key === "lr_bar_turns") return num5(row.lr_bar_turns); if (key.startsWith("ride_ht_")) { const k = key.replace("ride_ht_", ""); return num5(row[`ride_ht_${k}`]) ?? num5(row.as_found_rh?.[k]); } if (key.startsWith("feel_")) { const ph = key.replace("feel_", ""); return row[`feel_${ph}`] || traitToFeelChip(row[`trait_${ph}`], row.driver_feel); } return num5(row[key]); } function median2(values) { if (!values.length) return null; const s = values.slice().sort((a, b) => a - b); const mid = Math.floor(s.length / 2); return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2; } function medianFromRows(rows, key) { const vals = rows.map((r) => fieldKey(r, key)).filter((v) => v != null); return vals.length ? median2(vals) : null; } function parseBandMid(band) { if (band == null) return null; if (typeof band === "number") return band; const s = String(band); const m = s.match(/([\d.]+)\s*[-–]\s*([\d.]+)/); if (m) return (parseFloat(m[1]) + parseFloat(m[2])) / 2; const n = parseFloat(s.replace(/[^\d.]/g, "")); return Number.isFinite(n) ? n : null; } function fieldMeta(value, source, sourceLabel, confidence2 = "moderate") { return { value, source, sourceLabel, confidence: confidence2 }; } function staggerBandMid(profile2, bucket) { if (profile2 === "maxim_sprint_winged") { return parseBandMid(MAXIM_WINGED_SPRINT_BASELINE.stagger_rear_in[bucket] || MAXIM_WINGED_SPRINT_BASELINE.stagger_rear_in.unknown); } if (profile2 === "hyper_midget") { const stg = HYPER_MIDGET_BASELINE.stagger_in; if (bucket === "slick") return parseBandMid(stg.range) ? parseBandMid(stg.range) - 0.5 : 3.5; return parseBandMid(stg.start) ?? 4; } if (profile2 === "late_model" || profile2 === "modified_dirt") { const m = { tacky: 3, drying: 2.75, slick: 2.5, greasy: 3.25, unknown: 3 }; return m[bucket] ?? 3; } return null; } function getBuilderFieldDefault(builderKey, field, profile2, bucket) { const mfr = builderKey || "Builder"; if (field === "lr_bar_turns" && ["mach1", "jj", "krk", "fletcher"].includes(builderKey)) { return fieldMeta( bucket === "slick" ? 10.5 : 11, "builder_tendency", `Typical ${mfr} card reference \u2014 log your stop turns tonight`, "low" ); } if (field === "wing_angle" && profile2 !== "late_model" && profile2 !== "modified_dirt") { const base = bucket === "tacky" ? 18 : bucket === "slick" ? 17 : 17.5; return fieldMeta( base, "builder_tendency", `${mfr} community: wing ${bucket === "slick" ? "down vs tacky" : "baseline band"} when slicking`, "low" ); } if (field === "left_pct" && builderKey === "grt") { return fieldMeta(58, "builder_tendency", "GRT LM cross band starting point \u2014 log hot scale", "low"); } if (field === "rear_pct" && builderKey === "grt") { return fieldMeta(42, "builder_tendency", "GRT LM cross band starting point \u2014 log hot scale", "low"); } if (field === "left_pct" && builderKey === "harris") { return fieldMeta(56, "builder_tendency", "Harris/mod J-bar platform cross reference", "low"); } return null; } function getClassFieldDefault(field, profile2, bucket) { if (field === "stagger") { const v = staggerBandMid(profile2, bucket); if (v == null) return null; return fieldMeta( round22(v), "class_evolution", `${profile2.replace(/_/g, " ")} class band for ${bucketLabel(bucket)} nights`, "low" ); } if (field === "wing_angle" && profile2 !== "late_model" && profile2 !== "modified_dirt") { const v = bucket === "slick" ? 17 : 18; return fieldMeta(v, "class_evolution", `Winged class typical ${bucketLabel(bucket)} wing starting point`, "low"); } if (field === "lf_psi" && profile2 === "maxim_sprint_winged") { return fieldMeta(12, "class_evolution", "Sprint class LF hot psi starting band", "low"); } if (field === "rf_psi" && profile2 === "maxim_sprint_winged") { return fieldMeta(12, "class_evolution", "Sprint class RF hot psi starting band", "low"); } if (field === "lr_psi" && profile2 === "maxim_sprint_winged") { return fieldMeta(10, "class_evolution", "Sprint class LR hot psi starting band", "low"); } if (field === "rr_psi" && profile2 === "maxim_sprint_winged") { return fieldMeta(10, "class_evolution", "Sprint class RR hot psi starting band", "low"); } return null; } function resolveOneField(field, ctx) { const { log, bucket, m, builderKey, profile: profile2 } = ctx; const stateRows = log.filter((r) => similarBucket(rowBucket(r), bucket) || rowBucket(r) === bucket); const exactStateRows = log.filter((r) => rowBucket(r) === bucket); if (exactStateRows.length >= 2) { const v = medianFromRows(exactStateRows, field); if (v != null) { return fieldMeta( typeof v === "number" ? round22(v) : v, "history_track_state", `Based on your last ${exactStateRows.length} runs at this track (${bucketLabel(bucket)})`, "high" ); } } if (exactStateRows.length === 1) { const v = fieldKey(exactStateRows[0], field); if (v != null) { return fieldMeta( typeof v === "number" ? round22(v) : v, "history_track_state", `Your last run here (${bucketLabel(bucket)})`, "moderate" ); } } if (stateRows.length >= 2) { const v = medianFromRows(stateRows, field); if (v != null) { return fieldMeta( typeof v === "number" ? round22(v) : v, "history_similar_state", `Your average on similar ${bucketLabel(bucket)} nights (${stateRows.length} runs)`, "moderate" ); } } const trackRows = log.filter((r) => fieldKey(r, field) != null); if (trackRows.length >= 3) { const v = medianFromRows(trackRows, field); if (v != null) { return fieldMeta( typeof v === "number" ? round22(v) : v, "history_track", `Your average over ${trackRows.length} runs at this track`, "moderate" ); } } const setupKey = field === "lr_bar_turns" ? m.lr_bar_turns ?? m.bite : m[field]; if (setupKey != null && field !== "feel_entry" && field !== "feel_mid" && field !== "feel_exit") { return fieldMeta( typeof setupKey === "number" ? round22(setupKey) : setupKey, "car_setup", "From your setup sheet on file", "moderate" ); } const builderDef = getBuilderFieldDefault(builderKey, field, profile2, bucket); if (builderDef) return builderDef; return getClassFieldDefault(field, profile2, bucket); } function applyEvolutionAdjustments(fields, trend, profile2) { if (!trend || trend === "stable" || trend === "rubbering") return null; const notes = []; if (trend === "slicking") { if (fields.stagger?.value != null && typeof fields.stagger.value === "number") { const step = profile2 === "maxim_sprint_winged" ? -0.5 : profile2 === "hyper_midget" ? -0.25 : -0.125; fields.stagger = { ...fields.stagger, value: round22(fields.stagger.value + step), source: "evolution_adjust", sourceLabel: `${fields.stagger.sourceLabel} \xB7 \u2212${Math.abs(step)}" for slicking trend`, confidence: fields.stagger.confidence }; notes.push("stagger down for slicking"); } if (fields.wing_angle?.value != null && typeof fields.wing_angle.value === "number") { fields.wing_angle = { ...fields.wing_angle, value: round1(fields.wing_angle.value - 0.5), source: "evolution_adjust", sourceLabel: `${fields.wing_angle.sourceLabel} \xB7 \u22120.5\xB0 for slicking`, confidence: fields.wing_angle.confidence }; notes.push("wing down for slicking"); } } if (trend === "rubbering" && fields.stagger?.value != null && typeof fields.stagger.value === "number") { const step = profile2 === "late_model" || profile2 === "modified_dirt" ? 0.125 : 0.25; fields.stagger = { ...fields.stagger, value: round22(fields.stagger.value + step), source: "evolution_adjust", sourceLabel: `${fields.stagger.sourceLabel} \xB7 +${step}" for rubber build`, confidence: fields.stagger.confidence }; notes.push("stagger up for rubber"); } return notes.length ? notes.join(" \xB7 ") : null; } function applyOverrideBias(field, value, store, carId) { if (value == null || typeof value !== "number") return value; const bias = store?.bias?.[`${carId}:${field}`]; if (!bias || Math.abs(bias.mean) < 0.12) return value; return round22(value + bias.mean); } function recomputeOverrideBias(store = {}) { const bias = {}; const groups = {}; for (const e of store.entries || []) { if (e.suggested == null || e.actual == null) continue; const key = `${e.carId}:${e.field}`; if (!groups[key]) groups[key] = []; groups[key].push(Number(e.actual) - Number(e.suggested)); } for (const [key, deltas] of Object.entries(groups)) { const recent = deltas.slice(-12); if (recent.length < 2) continue; const mean2 = recent.reduce((a, b) => a + b, 0) / recent.length; bias[key] = { mean: round22(mean2), n: recent.length }; } return { ...store, bias }; } function recordCaptureOverride(store, entry) { const next = { version: 1, entries: [...store?.entries || [], { ...entry, ts: entry.ts || (/* @__PURE__ */ new Date()).toISOString() }].slice(-200) }; return recomputeOverrideBias(next); } function buildSmartCaptureDefaults(vc2 = {}, trackState, profile2, opts = {}) { if (!isSeriousCaptureProfile(profile2)) { return { active: false, values: {}, fields: {}, headline: null, evolutionNote: null }; } const log = opts.sessionLog || vc2.session_log || []; const bucket = normalizeTrackStateBucket(trackState); const m = vc2.setup_measurements || {}; const builder = detectBuilderKey(vc2, profile2); const evoCtx = buildEnrichedEvolutionContext( { session_log: log, track_snaps: vc2.track_snaps || [], vehicle_context: vc2 }, trackState ); const trend = evoCtx.analysis?.trend || evoCtx.analysis?.loggedTrend || null; const carId = opts.carId || vc2.car_id || "local"; const priorityFields = [ "stagger", "wing_angle", "lr_bar_turns", "left_pct", "rear_pct", "lf_psi", "rf_psi", "lr_psi", "rr_psi", "feel_entry", "feel_mid", "feel_exit", "ride_ht_lf", "ride_ht_rf", "ride_ht_lr", "ride_ht_rr" ]; const ctx = { log, bucket, m, builderKey: builder.key, profile: profile2 }; const fields = {}; for (const f of priorityFields) { const meta2 = resolveOneField(f, ctx); if (meta2) { const biased = typeof meta2.value === "number" ? applyOverrideBias(f, meta2.value, opts.overrideStore, carId) : meta2.value; fields[f] = { ...meta2, value: biased }; } } if (!fields.ride_ht_lf && vc2.as_found_rh?.lf != null) { fields.ride_ht_lf = fieldMeta(vc2.as_found_rh.lf, "car_setup", "Your last as-found LF ride height", "moderate"); } if (!fields.ride_ht_rf && vc2.as_found_rh?.rf != null) { fields.ride_ht_rf = fieldMeta(vc2.as_found_rh.rf, "car_setup", "Your last as-found RF ride height", "moderate"); } if (!fields.ride_ht_lr && vc2.as_found_rh?.lr != null) { fields.ride_ht_lr = fieldMeta(vc2.as_found_rh.lr, "car_setup", "Your last as-found LR ride height", "moderate"); } if (!fields.ride_ht_rr && vc2.as_found_rh?.rr != null) { fields.ride_ht_rr = fieldMeta(vc2.as_found_rh.rr, "car_setup", "Your last as-found RR ride height", "moderate"); } for (const k of ["lf", "rf", "lr", "rr"]) { const fk = `ride_ht_${k}`; if (!fields[fk] && m[fk] != null) { fields[fk] = fieldMeta(m[fk], "car_setup", `Setup sheet ${k.toUpperCase()} ride height`, "moderate"); } } const evolutionNote = applyEvolutionAdjustments(fields, trend, profile2); const values = {}; for (const [k, meta2] of Object.entries(fields)) { values[k] = meta2.value; } if (!values.session) values.session = log[0]?.session || "Heat"; if (!values.track_state) values.track_state = trackState || log[0]?.track_state || null; const topSources = ["stagger", "wing_angle", "lr_bar_turns", "lf_psi"].map((f) => fields[f]).filter(Boolean); const headline = topSources.length ? `Smart defaults: ${topSources.slice(0, 2).map((f) => f.sourceLabel.split("\xB7")[0].trim()).join(" \xB7 ")}` : "Smart defaults from class + track state \u2014 log tonight to train your history"; return { active: true, values, fields, headline, evolutionNote, trend, bucket }; } function smartDefaultsToForm(smart) { if (!smart?.active) return null; const v = smart.values; return { rf_psi: v.rf_psi, lf_psi: v.lf_psi, rr_psi: v.rr_psi, lr_psi: v.lr_psi, stagger: v.stagger, lr_bar_turns: v.lr_bar_turns, wing_angle: v.wing_angle, left_pct: v.left_pct, rear_pct: v.rear_pct, session: v.session, track_state: v.track_state, feel_entry: v.feel_entry, feel_mid: v.feel_mid, feel_exit: v.feel_exit, ride_ht_lf: v.ride_ht_lf, ride_ht_rf: v.ride_ht_rf, ride_ht_lr: v.ride_ht_lr, ride_ht_rr: v.ride_ht_rr, smartFields: smart.fields, smartHeadline: smart.headline, evolutionNote: smart.evolutionNote, fromSmartDefaults: true }; } function buildAdvancedPrefill(vc2 = {}, trackState, sessionLog = [], setupMeasurements = {}, opts = {}) { const profile2 = resolveCaptureProfile(vc2); if (!isSeriousCaptureProfile(profile2)) return null; const enriched = { ...vc2, setup_measurements: setupMeasurements || vc2.setup_measurements }; const smart = buildSmartCaptureDefaults(enriched, trackState, profile2, { sessionLog, overrideStore: opts.overrideStore, carId: opts.carId }); return smartDefaultsToForm(smart); } function diffCaptureOverrides(suggestedFields, actualValues, meta2 = {}) { const entries = []; for (const [field, metaObj] of Object.entries(suggestedFields || {})) { const suggested = metaObj?.value; const actual = actualValues[field]; if (suggested == null || actual == null) continue; if (typeof suggested === "number" && typeof actual === "number") { if (Math.abs(suggested - actual) < 0.05) continue; } else if (String(suggested) === String(actual)) continue; entries.push({ carId: meta2.carId, trackSlug: meta2.trackSlug, field, suggested, actual, trackState: meta2.trackState, source: metaObj.source }); } return entries; } // scripts/lib/experimental/advancedDataCapture.mjs var CAPTURE_TIER_LABELS = ["Baseline", "Scale depth", "Race-night ready", "Full reference"]; function isSeriousCaptureProfile(profile2) { return isAdvancedChassisProfile(profile2) || isLateModelEvolutionProfile(profile2); } function resolveCaptureProfile(vc2 = {}) { const cls = String(vc2.car_class || "").toLowerCase(); const ct = String(vc2.car_type || "").toLowerCase(); if (/late model|super late|crate late|lm/i.test(cls) || ct === "latemodel") return "late_model"; if (/modified|imca modified|sport mod|dirt mod/i.test(cls) || ct === "modified") return "modified_dirt"; if (/midget|usac/i.test(cls)) return "hyper_midget"; if (/410|360|305|sprint|winged/i.test(cls) || ct === "sprint") return "maxim_sprint_winged"; if (isAdvancedChassisProfile("maxim_sprint_winged") && vc2.winged) return "maxim_sprint_winged"; return null; } function sessionHasScale(row = {}) { return row.lf_psi != null && row.rf_psi != null || row.stagger != null || row.left_pct != null && row.rear_pct != null; } function sessionHasFeel(row = {}) { return Boolean(row.driver_feel || row.trait_entry || row.trait_mid || row.trait_exit); } function assessDataCaptureState(vc2 = {}, trackState, sessionLog = [], profile2 = "") { const assessment = assessAdvancedContext(vc2, trackState, profile2); const log = Array.isArray(sessionLog) ? sessionLog : []; const tonightRows = log.filter((r) => r && (r.source === "quick_log" || r.ts)); const lastRow = log[0] || null; const hasSessionTonight = tonightRows.length >= 1; const hasSessionDepth = log.length >= 2; const hasTrackStateOnLog = log.some((r) => r.track_state); const hasSnap = Array.isArray(vc2.track_snaps) && vc2.track_snaps.length > 0; const logHasFeel = log.some((r) => sessionHasFeel(r)); const hasFeel = assessment.hasFeel || logHasFeel; const winged = vc2.winged !== false && (profile2 === "maxim_sprint_winged" || profile2 === "hyper_midget" || /wing/i.test(String(vc2.car_class || ""))); return { ...assessment, hasFeel, profile: profile2, winged, hasSessionTonight, hasSessionDepth, hasTrackStateOnLog, hasSnap, sessionCount: log.length, lastSessionHasScale: lastRow ? sessionHasScale(lastRow) : false, lastSessionHasFeel: lastRow ? sessionHasFeel(lastRow) : false, captureTier: computeCaptureTier(assessment.completeness, hasSessionTonight, hasSessionDepth) }; } function feelChipToTrait(chip) { const m = { push: "tight", free: "loose", tight: "tight", neutral: "neutral" }; return m[String(chip || "").toLowerCase()] || null; } function traitToFeelChip(trait, driverFeel) { const t = String(trait || "").toLowerCase(); if (t === "loose") return "free"; if (t === "neutral") return "neutral"; if (t === "tight") return String(driverFeel || "").toLowerCase() === "push" ? "push" : "tight"; return null; } function suggestNextSessionLabel(lastSessionLabel) { const s = String(lastSessionLabel || "").toLowerCase(); if (/practice/.test(s)) return "Heat"; if (/heat|qual/.test(s)) return "Feature"; if (/b-main|bmain/.test(s)) return "Feature"; if (/feature|main|a-main/.test(s)) return "Feature"; return "Heat"; } function hasRepeatableRow(sessionLog = []) { return (sessionLog || []).some((r) => sessionHasScale(r)); } function rowToRepeatableForm(row = {}, vc2 = {}) { const m = vc2.setup_measurements || {}; return { rf_psi: num6(row.rf_psi) ?? num6(m.rf_psi), lf_psi: num6(row.lf_psi) ?? num6(m.lf_psi), rr_psi: num6(row.rr_psi) ?? num6(m.rr_psi), lr_psi: num6(row.lr_psi) ?? num6(m.lr_psi), stagger: num6(row.stagger) ?? num6(m.stagger), lr_bar_turns: num6(row.lr_bar_turns) ?? num6(m.lr_bar_turns), wing_angle: num6(row.wing_angle) ?? num6(m.wing_angle), left_pct: num6(row.left_pct) ?? num6(m.left_pct), rear_pct: num6(row.rear_pct) ?? num6(m.rear_pct), track_state: row.track_state || null, session: suggestNextSessionLabel(row.session), feel_entry: row.feel_entry || traitToFeelChip(row.trait_entry, row.driver_feel), feel_mid: row.feel_mid || traitToFeelChip(row.trait_mid, row.driver_feel), feel_exit: row.feel_exit || traitToFeelChip(row.trait_exit, row.driver_feel), fromHistory: true }; } function computeCaptureTier(completeness, hasSessionTonight, hasSessionDepth) { if (completeness >= 85 && hasSessionDepth) return 3; if (completeness >= 60 && hasSessionTonight) return 2; if (completeness >= 30 || hasSessionTonight) return 1; return 0; } function buildCapturePriorities(state) { if (!state || !isSeriousCaptureProfile(state.profile)) return []; const priors = []; const push = (item) => { if (!item.filled) priors.push(item); }; push({ id: "session_baseline", label: "Tonight's scale row", effort: "low", source: "quick_log", value: "Powers between-session drift, builder signals, and A-B-A interpretation", unlocks: ["builder_signal", "track_evolution", "night_summary"], filled: state.hasSessionTonight && state.lastSessionHasScale, cta: "Log psi + stagger in Quick-Log" }); push({ id: "track_state", label: "Track state on this row", effort: "low", source: "quick_log", value: "Evolution + wing/bar sequencing only count on matched state", unlocks: ["track_evolution", "aba_same_state"], filled: state.hasTrack && state.hasTrackStateOnLog, cta: "Set tacky / slick when you log" }); push({ id: "chassis_builder", label: "Chassis builder on file", effort: "low", source: "setup", value: "Routes Mach 1 / J&J / KRK / GRT builder signals to your car", unlocks: ["builder_signal", "chassis_baseline"], filled: state.hasChassis, cta: "Garage \u2192 Setup \u2192 Chassis" }); if (state.winged) { push({ id: "bar_turns", label: "LR/RR bar stop turns", effort: "low", source: "quick_log", value: "Builder cards compare nights by turn count \u2014 required for clean A-B-A", unlocks: ["builder_signal", "advanced_geometry"], filled: state.hasBarTurns || state.lastSessionHasScale && state.measCount >= 3, cta: "Add bar turns to scale sheet or Quick-Log" }); push({ id: "phase_feel", label: "Entry / exit feel", effort: "low", source: "quick_log", value: "Unlocks phase-first try-first (wing vs bar) on Generate", unlocks: ["try_first", "advanced_prioritization"], filled: state.hasFeel || state.lastSessionHasFeel, cta: "Tap PUSH / TIGHT / FREE after the run" }); } else if (isLateModelEvolutionProfile(state.profile)) { push({ id: "cross_wedge", label: "Cross % or wedge reference", effort: "low", source: "quick_log", value: "Late model evolution plans wedge/stagger per session \u2014 needs a baseline row", unlocks: ["late_model_evolution", "builder_signal"], filled: state.hasCoreMeas && state.measCount >= 3, cta: "Log left/rear % or hot psi row" }); } push({ id: "track_snap", label: "Track snap (1 tap)", effort: "low", source: "track_snap", value: "Fills evolution gaps when you skip full session notes", unlocks: ["track_evolution", "conflict_resolution"], filled: state.hasSnap, cta: "Tap SLICK+ / RUBBER+ when the track changes" }); if (state.captureTier >= 2 && state.hasDeepBuilder) { const tierInfo = resolvePivotTier(vc, profile); if (tierInfo.tier === PIVOT_TIER.FULL) { push({ id: "pivot_pickups", label: "PIVOT pickup points", effort: "medium", source: "pivot", value: "Repeatable RC geometry between nights \u2014 builder-level comparisons", unlocks: ["advanced_geometry", "geometry_state"], filled: state.hasGeometryState, cta: "PIVOT \u2192 inner A-arms + shock mounts + rear pickups" }); } else if (tierInfo.tier === PIVOT_TIER.FOCUSED) { push({ id: "pivot_refs", label: "Geometry refs (focused PIVOT)", effort: "low", source: "pivot", value: "J-bar/panhard/rear refs sharpen micro/lightning builder priors", unlocks: ["builder_reference", "geometry_awareness"], filled: state.hasGeometryState, cta: "Setup \u2192 log J-ladder + panhard + rear notes" }); } } if (state.captureTier >= 2) { push({ id: "afrh_ride", label: "As-found ride heights", effort: "medium", source: "afrh", value: "Detects scale-to-paddock drift before you chase setup", unlocks: ["rh_drift", "advanced_context"], filled: state.hasAfrh, cta: "AFRH after scale \u2014 4 corners" }); } return priors.slice(0, 4); } function prefillFromHistory(vc2 = {}, sessionLog = [], setupMeasurements = {}, opts = {}) { const trackState = opts.trackState ?? sessionLog[0]?.track_state ?? null; const advanced = buildAdvancedPrefill(vc2, trackState, sessionLog, setupMeasurements, opts); if (advanced) return advanced; const last = sessionLog[0] || {}; const m = setupMeasurements || vc2.setup_measurements || {}; const out = { rf_psi: num6(last.rf_psi) ?? num6(m.rf_psi) ?? num6(vc2.rf_psi), lf_psi: num6(last.lf_psi) ?? num6(m.lf_psi) ?? num6(vc2.lf_psi), rr_psi: num6(last.rr_psi) ?? num6(m.rr_psi) ?? num6(vc2.rr_psi), lr_psi: num6(last.lr_psi) ?? num6(m.lr_psi) ?? num6(vc2.lr_psi), stagger: num6(last.stagger) ?? num6(m.stagger), lr_bar_turns: num6(last.lr_bar_turns) ?? num6(m.lr_bar_turns) ?? num6(m.bite), wing_angle: num6(last.wing_angle) ?? num6(m.wing_angle), left_pct: num6(last.left_pct) ?? num6(m.left_pct), rear_pct: num6(last.rear_pct) ?? num6(m.rear_pct), session: last.session || guessSessionPhase(sessionLog), track_state: last.track_state || null, feel_entry: last.feel_entry || traitToFeelChip(last.trait_entry, last.driver_feel), feel_mid: last.feel_mid || traitToFeelChip(last.trait_mid, last.driver_feel), feel_exit: last.feel_exit || traitToFeelChip(last.trait_exit, last.driver_feel), fromHistory: Boolean(last.rf_psi != null || last.lf_psi != null || last.stagger != null) }; return out; } function guessSessionPhase(sessionLog = []) { const labels = sessionLog.map((r) => String(r.session || "").toLowerCase()); if (labels.some((s) => /feature|main|a-main/.test(s))) return "Feature"; if (labels.some((s) => /heat|qual/.test(s))) return "Heat"; return "Heat"; } function num6(v) { const n = Number(v); return Number.isFinite(n) ? n : null; } function buildPostCaptureFeedback(before, after, loggedFields = []) { const unlocked = []; if (!before?.hasSessionTonight && after?.hasSessionTonight) { unlocked.push("Between-session trends + night summary"); } if (!before?.hasCoreMeas && after?.hasCoreMeas) { unlocked.push("Builder-specific signals"); } if (!before?.hasTrackStateOnLog && after?.hasTrackStateOnLog) { unlocked.push("Track-state-matched evolution planning"); } if (!before?.hasFeel && after?.hasFeel) { unlocked.push("Phase-first try-first on Generate"); } if (!before?.hasSnap && after?.hasSnap) { unlocked.push("Passive evolution when session log is thin"); } if (!before?.hasBarTurns && after?.hasBarTurns) { unlocked.push("Bar-turn A-B-A reference"); } if (loggedFields.includes("track_snap") && unlocked.length === 0) { unlocked.push("Track evolution priors on next Generate"); } if (loggedFields.includes("pivot") && after?.pivotBenefit) { unlocked.push(after.pivotBenefit); } if (loggedFields.includes("platform") && after?.platformStale) { unlocked.push("PIVOT flagged stale \u2014 re-measure pickups for accurate geometry"); } const tierUp = after && before && after.captureTier > before.captureTier; const headline = unlocked.length ? `Unlocked: ${unlocked.slice(0, 2).join(" \xB7 ")}` : tierUp ? "Capture tier up \u2014 stronger recs on Generate" : "Logged \u2014 regenerate Crew Chief to apply"; const next = buildCapturePriorities(after)[0]; return { headline, unlocked, nextGap: next ? { label: next.label, value: next.value, cta: next.cta } : null, tier: after?.captureTier ?? 0, tierLabel: CAPTURE_TIER_LABELS[after?.captureTier ?? 0] || "Baseline" }; } function buildAdvancedDataCaptureCoachPayload(vc2 = {}, trackState, profile2) { if (!isSeriousCaptureProfile(profile2)) { return { active: false, priorities: [], tier: 0, tierLabel: "Baseline" }; } const sessionLog = vc2.session_log || []; const state = assessDataCaptureState(vc2, trackState, sessionLog, profile2); const priorities = buildCapturePriorities(state); const top = priorities[0] || null; const pivot = assessPivotCompleteness(vc2, vc2.car_type, profile2); const tierInfo = resolvePivotTier(vc2, profile2); const pivotCoach = buildPivotCoachPayload(vc2, profile2); return { active: true, profile: profile2, state, priorities, tier: state.captureTier, tierLabel: CAPTURE_TIER_LABELS[state.captureTier] || "Baseline", completeness: state.completeness, headline: top ? `Next: ${top.label} \u2014 ${top.value.split("\u2014")[0].trim()}` : "Reference row on file \u2014 log each session to keep evolution + A-B-A sharp", topPriority: top, pivotLine: pivot.coachLine, pivotStale: pivot.stale, pivotComplete: pivot.complete, pivotMeasured: pivot.measured, pivotExpected: pivot.expected, pivotTier: tierInfo.tier, pivotTierLabel: tierInfo.label, showPivotButton: pivotCoach.showPivotButton, showFocusedButton: pivotCoach.showFocusedButton, showPlatformButton: pivotCoach.showPlatformButton }; } function buildQuickLogCaptureHint(vc2 = {}, trackState, profile2) { const payload = buildAdvancedDataCaptureCoachPayload(vc2, trackState, profile2); if (!payload.active || !payload.topPriority) { return payload.active ? "Log scale + feel \u2014 your row beats every prior on Generate" : null; } const p = payload.topPriority; return `${p.cta} \u2192 ${p.unlocks[0]?.replace(/_/g, " ") || "better recs"}`; } // scripts/lib/experimental/classVariantRegistry.mjs function norm3(s) { return String(s || "").toLowerCase(); } var CLASS_VARIANTS = { junior_sprint: { slug: "junior_sprint", label: "Junior Sprint", carType: "jrsprint", baselineProfile: "junior_sprint", pivotGrassroots: "junior_sprint", engine: "204cc Briggs World Formula (sealed)", tireDiameter: '8"', tireNote: 'Hoosier Jr Sprint 8" \u2014 not 10" micro or full sprint', wing: "Scaled junior wing \u2014 smaller chord/angle steps than 600 micro", shockNote: "Smaller shock bodies (often 5-way) \u2014 lighter valving than 600 micro", weightLb: "450\u2013550 with driver (verify series)", staggerRearIn: '2\u20134" typical', notInterchangeableWith: ["600 micro", "270 micro", "USAC midget", "quarter midget"], match: [/junior sprint/, /\bjr\.?\s*sprint\b/] }, micro_sprint_270: { slug: "micro_sprint_270", label: "270 Micro Sprint", carType: "micro270", baselineProfile: "micro_sprint_270", pivotGrassroots: "micro_sprint_270", engine: "270cc motorcycle engine (KTM/Honda/Yamaha \u2014 verify sealed/open)", tireDiameter: '10" (smaller rollout than 600)', tireNote: "Hyper 270 guide \u2014 not 600cc roll-out charts", wing: "Winged programs use smaller wing than open 600 \u2014 verify division", shockNote: "Lighter car than 600 \u2014 smaller shock/can packages; do not copy 600 valving", weightLb: "650\u2013800 with driver (verify series)", staggerRearIn: '3\u20136" typical', notInterchangeableWith: ["600 micro", "Junior Sprint", "full sprint"], match: [/\b270\b.*micro/, /micro.*\b270\b/, /\b270cc\b/] }, micro_sprint_250: { slug: "micro_sprint_250", label: "250 Micro Sprint", carType: "micro270", baselineProfile: "micro_sprint_270", pivotGrassroots: "micro_sprint_270", engine: "250cc motorcycle engine \u2014 regional spec", tireDiameter: '10" (250/270 platform)', tireNote: "Same chassis family as 270 \u2014 verify local 250 vs 270 rulebook", wing: "Regional wing rules \u2014 often winged on 1/8\u20131/4 mi", shockNote: "250/270 shock packages \u2014 lighter than 600", weightLb: "600\u2013750 with driver (verify series)", staggerRearIn: '3\u20135" typical', notInterchangeableWith: ["600 micro", "Junior Sprint"], match: [/\b250\b.*micro/, /micro.*\b250\b/, /\b250cc\b.*micro/] }, micro_sprint_600: { slug: "micro_sprint_600", label: "600 Micro Sprint", carType: "micro", baselineProfile: "micro_sprint_600", pivotGrassroots: "micro_sprint_600", engine: "600cc motorcycle engine (POWRi/regional sealed/spec)", tireDiameter: '10" Hyper micro spec', tireNote: "Hyper 600 manual roll-out \u2014 D10/D12/D15 dirt ladder", wing: "Winged vs non-wing divisions differ \u2014 verify before setup", shockNote: "Hyper manual valving per torsion/coil/wishbone \u2014 one click at a time", weightLb: "750\u2013900 with driver (verify series)", staggerRearIn: '4\u20136" winged typical (NOW600 A-class higher)', notInterchangeableWith: ["270 micro", "Junior Sprint", "410 sprint"], match: [/600 micro/, /restricted micro/, /now600/, /\b600cc\b.*micro/] }, micro_turf: { slug: "micro_turf", label: "Turf Tire Micro Sprint", carType: "microturf", baselineProfile: "micro_turf", pivotGrassroots: "micro_turf", engine: "250\u2013600cc per division \u2014 verify turf series (often 270/600)", tireDiameter: "Turf/compound spec \u2014 not dirt Hoosier D-series", tireNote: "Indoor/outdoor turf tracks \u2014 compound and psi primary; inch stagger secondary", wing: "Often limited or no wing on turf \u2014 verify venue rules", shockNote: "Turf = high grip, low compliance \u2014 softer platform, less LR stop vocabulary", weightLb: "Verify turf series minimum", staggerRearIn: "Minimal on turf \u2014 psi and compound before inch stagger", notInterchangeableWith: ["dirt 600 micro setup sheet", "dirt sprint stagger bands"], match: [/turf.*micro/, /micro.*turf/, /turf tire micro/, /indoor micro/, /turf sprint/] }, mod_lite: { slug: "mod_lite", label: "Mod Lite", carType: "modlite", baselineProfile: "mod_lite", pivotGrassroots: null, engine: "600cc or spec limited motor \u2014 not A-mod open", tireDiameter: "Mod lite spec \u2014 smaller than full modified", tireNote: "Mini-mod tire rules \u2014 not micro sprint or full mod tires", wing: "Non-wing standard", shockNote: "Coilover or leaf per builder \u2014 cross range smaller than A-mod", weightLb: "1,000\u20131,200 with driver (verify series)", staggerRearIn: "Lower band than A-mod \u2014 verify rulebook", notInterchangeableWith: ["600 micro", "IMCA Modified", "late model"], match: [/mod lite/, /modlite/, /600 mini mod/, /mini modified/, /powri mod lite/] }, sprint_360_sportsman: { slug: "sprint_360_sportsman", label: "ASCS Sportsman / 360 Sportsman", carType: "sprint", baselineProfile: "sprint_360_sportsman", pivotGrassroots: null, engine: "360ci restricted/sportsman rules \u2014 less tuning freedom than open 360", tireDiameter: "Sprint spec Hoosier D12/D15/D25", tireNote: "Same tire ladder as 360 winged \u2014 less RR heat than 410", wing: "Wing required on winged sportsman programs", shockNote: "Torsion bar sprint ladder \u2014 wing and stagger before LR stop", weightLb: "~1,350+ with driver (verify ASCS/regional book)", staggerRearIn: '7\u201310" winged typical \u2014 same band as 360 but conservative wing steps', notInterchangeableWith: ["602 crate late model", "410 open sprint power tuning"], match: [/ascs sportsman/, /360 sportsman/, /sportsman 360/, /sportsman sprint/, /regional sportsman.*360/] } }; function resolveClassVariant(vc2 = {}) { const cls = norm3(vc2.car_class || vc2.class || vc2.class_name); const ct = norm3(vc2.car_type); if (ct === "jrsprint" || CLASS_VARIANTS.junior_sprint.match.some((re) => re.test(cls))) { return "junior_sprint"; } if (CLASS_VARIANTS.micro_turf.match.some((re) => re.test(cls)) || ct === "microturf") { return "micro_turf"; } if (CLASS_VARIANTS.mod_lite.match.some((re) => re.test(cls)) || ct === "modlite") { return "mod_lite"; } if (CLASS_VARIANTS.sprint_360_sportsman.match.some((re) => re.test(cls))) { return "sprint_360_sportsman"; } if (CLASS_VARIANTS.micro_sprint_250.match.some((re) => re.test(cls))) { return "micro_sprint_250"; } if (ct === "micro270" || CLASS_VARIANTS.micro_sprint_270.match.some((re) => re.test(cls))) { return "micro_sprint_270"; } if (/lightning/.test(cls) || ct === "lightningsprint") { return "lightning_sprint"; } if (CLASS_VARIANTS.micro_sprint_600.match.some((re) => re.test(cls)) || ct === "micro" && !/\b270\b|\b250\b|turf/.test(cls)) { return "micro_sprint_600"; } return null; } function resolveBaselineProfileFromVariant(vc2 = {}) { const v = resolveClassVariant(vc2); if (!v || v === "lightning_sprint") return v === "lightning_sprint" ? "lightning_sprint" : null; return CLASS_VARIANTS[v]?.baselineProfile || null; } function isJuniorSprintVariant(vc2 = {}) { return resolveClassVariant(vc2) === "junior_sprint"; } function isMicro270Variant(vc2 = {}) { const v = resolveClassVariant(vc2); return v === "micro_sprint_270" || v === "micro_sprint_250"; } function isMicro600Variant(vc2 = {}) { return resolveClassVariant(vc2) === "micro_sprint_600"; } function isMicroTurfVariant(vc2 = {}) { return resolveClassVariant(vc2) === "micro_turf"; } function isModLiteVariant(vc2 = {}) { return resolveClassVariant(vc2) === "mod_lite"; } function isAscsSportsmanVariant(vc2 = {}) { return resolveClassVariant(vc2) === "sprint_360_sportsman"; } function buildClassVariantCoachLine(vc2 = {}) { const v = resolveClassVariant(vc2); if (!v) return null; const spec = CLASS_VARIANTS[v]; if (!spec) return null; return `${spec.label}: ${spec.engine} \xB7 ${spec.tireDiameter} tires \xB7 ${spec.wing} \u2014 not interchangeable with ${spec.notInterchangeableWith.slice(0, 2).join(" or ")}`; } return __toCommonJS(browser_entry_exports); })();