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, { assessGrassrootsContext: () => assessGrassrootsContext, buildGrassrootsCarSummaryLine: () => buildGrassrootsCarSummaryLine, buildGrassrootsContextGapLine: () => buildGrassrootsContextGapLine, crewChiefReport: () => crewChiefReport, deriveInstrumentation: () => deriveInstrumentation, formatTrackConditionSummary: () => formatTrackConditionSummary, isGrassrootsDayOneProfile: () => isGrassrootsDayOneProfile, normalizeTrackContext: () => normalizeTrackContext, recommend: () => recommend, renderCrewChief: () => renderCrewChief, renderMarkdown: () => renderMarkdown, renderRecommendations: () => renderRecommendations, reportJSON: () => reportJSON, rowToRecord: () => rowToRecord, rowsToRecords: () => rowsToRecords }); // 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/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) { 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 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(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 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 PHILOSOPHY = { 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 SCALING = { 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" }; 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 withCaveat(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 ?? 10; const track = normalizeTrack(trackState); const priors = [ PHILOSOPHY, PANHARD, SQUARING_REAR, SQUARING_FRONT, BIRDCAGE_CASTER, RIDE_HEIGHT, PSI, SCALING, TRACK_WIDTH, track === "slick" || track === "wet" ? TRACK_LOOSE : TRACK_TIGHT, 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 withCaveat(priors).slice(0, max); } function pickStanleyRsrPhilosophyLine() { return PHILOSOPHY.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 normalizeTrack2(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 withCaveat2(priors) { return priors.map((p) => ({ ...p, caveat: BULLRIDER_QM_CAVEAT })); } var PHILOSOPHY2 = { 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 SCALING2 = { 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 staggerPrior(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_TUNING = { 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 = normalizeTrack2(trackState); const priors = [ PHILOSOPHY2, SQUARING_BLOCKS, PANHARD_LR, BIRDCAGE_LR, BIRDCAGE_RR, SQUARING_REAR2, SQUARING_FRONT2, CASTER_CAMBER_TOE, RIDE_HEIGHT2, PSI2, SCALING2, TRACK_WIDTH2, staggerPrior(track), track === "slick" || track === "wet" ? TRACK_LOOSE2 : TRACK_TIGHT2, track === "slick" ? DIRECTION_TIGHT2 : DIRECTION_LOOSE2, TRACK_BIKING2, PHASE_TUNING ]; 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 withCaveat2(priors).slice(0, max); } function pickBullriderPhilosophyLine() { return PHILOSOPHY2.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 normalizeTrack3(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 withCaveat3(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 PHILOSOPHY3 = { 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 SCALING3 = { 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 staggerPrior2(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_TUNING2 = { 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 = normalizeTrack3(trackState); const wb = wheelbaseInches(enrichedVc); const priors = [ PHILOSOPHY3, 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, SCALING3, STEERING, SPRING_BASELINE, staggerPrior2(track), track === "slick" ? TRACK_PUSH : TRACK_LOOSE3, track === "slick" ? DIRECTION_TIGHT3 : DIRECTION_LOOSE3, TEMP_TUNING, ALIGNMENT_KIT, PHASE_TUNING2 ]; return withCaveat3(priors).slice(0, max); } function pickNcPhilosophyLine() { return PHILOSOPHY3.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 normalizeTrack4(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 withCaveat4(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_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 staggerPrior3(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 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 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_PSI = { lever: "qrc_psi", action: "QRC tire psi (tread community): log cold/hot \xB7 suspended tread often \u22646.5 psi hot on RS \xB7 higher RS loosens exit \xB7 lower RS tightens \xB7 match stagger circumference before psi hero moves", reason: "Community (QRC/suspended tread): psi shifts effective cross \u2014 tires before geometry stack on race night" }; 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, vc) => { const isSenior = /senior|sr|375|390|425|450|500|open|unrestricted/i.test( `${vc.car_class || ""} ${vc.class_name || ""}` ); return [ SLACK_PHILOSOPHY, SLACK_RIDE_HEIGHT, SLACK_GEOMETRY, COMMUNITY_SQUARING, COMMUNITY_GEOMETRY, isSenior ? SLACK_SCALING_SR : SLACK_SCALING_JR, COMMUNITY_NOSE_SEAT, crossPrior(track, "high"), COMMUNITY_LEFT_SIDE, staggerPrior3(track), COMMUNITY_WING, COMMUNITY_RIDE_HEIGHT, COMMUNITY_AXLE, track === "slick" ? COMMUNITY_TIGHTEN : COMMUNITY_LOOSEN, SLACK_TUNING, COMMUNITY_TIRE, COMMUNITY_PHASE, COMMUNITY_TRACK_CONDITIONS ]; }, toigo: (track) => [ TOIGO_PHILOSOPHY, TOIGO_BUILD, TOIGO_SCALING_ORDER, TOIGO_SCALING, TOIGO_SEAT, TOIGO_LEFT_SIDE, TOIGO_CROSS_METHOD, TOIGO_STAGGER_CROSS, TOIGO_FRONT_END, QRC_TOIGO_WINGED_SEAT, QRC_TOIGO_WINGED_AXLE, crossPrior(track, "high"), QRC_TOIGO_WINGED_RF, COMMUNITY_SQUARING, staggerPrior3(track), QRC_TOIGO_WINGED_STAGGER, COMMUNITY_RIDE_HEIGHT, COMMUNITY_WING, TOIGO_RESPONSE, track === "slick" ? COMMUNITY_TIGHTEN : TOIGO_DIRECTION, TOIGO_LOGGING, COMMUNITY_PHASE, COMMUNITY_TIRE, QRC_VS_TOIGO ], phantom: (track) => [ PHANTOM_PHILOSOPHY, PHANTOM_SEAT_CRADLE, PHANTOM_SCALING, PHANTOM_GEOMETRY, PHANTOM_RIDE_HEIGHT, COMMUNITY_SQUARING, PHANTOM_WING, crossPrior(track, "high"), COMMUNITY_LEFT_SIDE, staggerPrior3(track), COMMUNITY_WING, COMMUNITY_AXLE, track === "wet" ? COMMUNITY_LOOSEN : COMMUNITY_TIGHTEN, COMMUNITY_PHASE, COMMUNITY_TIRE, COMMUNITY_TRACK_CONDITIONS ], qrc: (track) => [ QRC_PHILOSOPHY, QRC_SETUPS_PDF, QRC_POSITIONING, QRC_SQUARING, QRC_FRONT_END, QRC_CASTer, QRC_RIDE_HEIGHT, QRC_SCALING, QRC_NOSE, QRC_SUSPENDED, crossPrior(track, "low"), QRC_TOIGO_WINGED_SEAT, QRC_TOIGO_WINGED_AXLE, COMMUNITY_LEFT_SIDE, qrcStaggerPrior(track), QRC_TOIGO_WINGED_STAGGER, QRC_TOIGO_WINGED_RF, QRC_PSI, COMMUNITY_WING, QRC_DIRECTION, track === "slick" ? COMMUNITY_TIGHTEN : COMMUNITY_LOOSEN, COMMUNITY_PHASE, COMMUNITY_TIRE, QRC_VS_TOIGO ], ultramax: (track) => [ ULTRAMAX_PHILOSOPHY, ULTRAMAX_SCALING, WINGED_GENERAL_PHILOSOPHY, COMMUNITY_NOSE_SEAT, COMMUNITY_GEOMETRY, COMMUNITY_SQUARING, crossPrior(track, "high"), staggerPrior3(track), COMMUNITY_AXLE, COMMUNITY_WING, COMMUNITY_RIDE_HEIGHT, track === "slick" ? COMMUNITY_TIGHTEN : COMMUNITY_LOOSEN, COMMUNITY_TIRE, COMMUNITY_TRACK_CONDITIONS ], chaos: (track) => [ CHAOS_PHILOSOPHY, CHAOS_SCALING, crossPrior(track, "low"), COMMUNITY_NOSE_SEAT, COMMUNITY_GEOMETRY, staggerPrior3(track), COMMUNITY_TIRE, COMMUNITY_AXLE, COMMUNITY_WING, track === "slick" ? COMMUNITY_TIGHTEN : COMMUNITY_LOOSEN, COMMUNITY_PHASE, COMMUNITY_TRACK_CONDITIONS ] }; var PHILOSOPHY_LINES = { slack: SLACK_PHILOSOPHY.action, toigo: TOIGO_PHILOSOPHY.action, phantom: PHANTOM_PHILOSOPHY.action, qrc: QRC_PHILOSOPHY.action, ultramax: ULTRAMAX_PHILOSOPHY.action, chaos: CHAOS_PHILOSOPHY.action }; function isOutlawDeepBuilder(mfrKey) { return DEEP_BUILDERS.has(mfrKey); } function outlawKartChassisPriors(mfrKey, enrichedVc = {}, trackState, opts = {}) { const maxDefault = mfrKey === "qrc" || mfrKey === "toigo" ? 18 : 16; const max = opts.maxPriors ?? maxDefault; const fn = BUILDER_PRIORS[mfrKey]; if (!fn) return []; const track = normalizeTrack4(trackState); const priors = fn(track, enrichedVc); return withCaveat4(priors).slice(0, max); } function pickOutlawPhilosophyLine(mfrKey) { return PHILOSOPHY_LINES[mfrKey] || WINGED_GENERAL_PHILOSOPHY.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 normalizeTrack5(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(vc = {}) { const c = String(vc.car_class || vc.class_name || "").toLowerCase(); if (vc.winged === false) return true; if (/non.?wing|wingless|no.?wing/.test(c)) return true; return false; } function isZLink(vc = {}) { const text = `${vc.chassis_name || ""} ${vc.chassis_model || ""}`.toLowerCase(); return /z-link|z link|zlink/.test(text); } function withCaveat5(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_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 ?? 10; const track = normalizeTrack5(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 ].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 withCaveat5(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 withCaveat5(priors).slice(0, max); } function pickHyperPhilosophyLine(profile) { if (profile === "lightning_sprint") return LIGHTNING_PHILOSOPHY.action; return MICRO_PHILOSOPHY.action; } // 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" } ], micro_sprint_600: [ { 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" } }, micro_sprint_600: { 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(vc = {}) { return [ vc.chassis_name, vc.chassis_mfr, vc.chassis_model, vc.chassis_manufacturer ].filter(Boolean).join(" "); } function detectGrassrootsChassisManufacturer(profile, vc = {}) { const text = norm(combinedChassisText(vc)); if (!text) return { key: null, mfr: null, confidence: "missing" }; const patterns = MFR_PATTERNS[profile] || []; for (const p of patterns) { if (p.re.test(text)) { return { key: p.key, mfr: p.mfr, confidence: "high" }; } } if (vc.chassis_philosophy) { return { key: "philosophy", mfr: vc.chassis_mfr || "Chassis", confidence: "medium" }; } return { key: null, mfr: null, confidence: "missing" }; } function detectGrassrootsChassisModel(mfrKey, vc = {}) { const text = norm(combinedChassisText(vc)); const patterns = MODEL_PATTERNS[mfrKey] || []; for (const p of patterns) { if (p.re.test(text)) return { model: p.model, generation: p.model }; } if (vc.chassis_model) return { model: String(vc.chassis_model), generation: null }; return { model: null, generation: null }; } function enrichGrassrootsVehicleContext(vc = {}, profile = "") { const mfr = detectGrassrootsChassisManufacturer(profile, vc); const modelInfo = mfr.key ? detectGrassrootsChassisModel(mfr.key, vc) : { model: null, generation: null }; const profileKey = mfr.key ? modelInfo.model ? `${mfr.key}_${modelInfo.model.toLowerCase().replace(/[\s-]+/g, "_")}` : mfr.key : null; const measurements = vc.setup_measurements && typeof vc.setup_measurements === "object" ? { ...vc.setup_measurements } : null; return { ...vc, chassis_manufacturer: mfr.mfr || vc.chassis_mfr || null, chassis_model: modelInfo.model || vc.chassis_model || null, chassis_generation: modelInfo.generation || vc.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: vc.setup_style || vc.front_susp || null, front_susp: vc.front_susp || vc.setup_style || null }; } function measurementContextPrior(vc) { const m = vc.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(vc) { const mods = [vc.geo_notes, vc.user_mods, vc.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(profile, enrichedVc = {}, trackState) { if (!profile || 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 (${profile}): ${enrichedVc.chassis_philosophy}` }]; } return []; } if (profile === "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 (profile === "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 (profile === "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 (profile === "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 (profile === "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 (profile === "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[profile]?.[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(profile, vc = {}) { const enriched = enrichGrassrootsVehicleContext(vc, profile); if (profile === "quarter_midget" && enriched.chassis_mfr_key === "stanley") { return pickStanleyRsrPhilosophyLine(); } if (profile === "quarter_midget" && enriched.chassis_mfr_key === "bullrider") { return pickBullriderPhilosophyLine(); } if (profile === "quarter_midget" && enriched.chassis_mfr_key === "nc") { return pickNcPhilosophyLine(); } if (profile === "outlaw_kart" && isOutlawDeepBuilder(enriched.chassis_mfr_key)) { return pickOutlawPhilosophyLine(enriched.chassis_mfr_key); } if (enriched.chassis_mfr_key === "hyper") { return pickHyperPhilosophyLine(profile); } const priors = grassrootsChassisManufacturerPriors(profile, enriched); const first = priors.find((p) => p.lever === "chassis_mfr" || p.lever.includes("philosophy")); if (!first) return enriched.chassis_philosophy || null; return `${first.action} \u2014 ${first.reason.replace(/^public baseline[^:]*:\s*/i, "")}`; } function resolveGrassrootsChassisDeepCaveat(profile, enrichedVc = {}) { const key = enrichedVc.chassis_mfr_key; if (profile === "quarter_midget" && key === "stanley") return STANLEY_RSR_QM_CAVEAT; if (profile === "quarter_midget" && key === "bullrider") return BULLRIDER_QM_CAVEAT; if (profile === "quarter_midget" && key === "nc") return NC_QM_CAVEAT; if ((profile === "micro_sprint_600" || profile === "lightning_sprint") && key === "hyper") { return HYPER_RACING_CAVEAT; } if (profile === "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(vc = {}) { return resolveGrassrootsChassisHint("outlaw_kart", vc); } 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 || {}); 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 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" }); } } 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(vc = {}) { return resolveGrassrootsChassisHint("quarter_midget", vc); } 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(vc = {}) { return resolveGrassrootsChassisHint("micro_sprint_600", vc); } 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(vc = {}, cls = "", carType = "") { const c = String(cls || vc.car_class || vc.class_name || "").toLowerCase(); const ct = String(carType || vc.car_type || "").toLowerCase(); if (/lightning/.test(c)) return false; if (/jr\.?\s*sprint|junior sprint/.test(c) || ct === "jrsprint") return false; if (/mini midget|micro midget/.test(c) && !/micro sprint/.test(c)) return false; if (ct === "micro") return true; if (/600 micro|restricted micro|now600|270 micro|\bmicro sprint/.test(c)) return true; if (/\b270\b/.test(c) && /micro|sprint/.test(c)) return true; if (/\bmicro\b/.test(c) && /600|270|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 || {}); 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/lightningSprintBaselinePriors.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 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(vc = {}, cls = "", carType = "") { const c = String(cls || vc.car_class || vc.class_name || "").toLowerCase(); const ct = String(carType || vc.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(vc = {}) { return resolveGrassrootsChassisHint("lightning_sprint", vc); } function isWingless2(vc = {}) { const c = String(vc.car_class || vc.class_name || "").toLowerCase(); if (vc.winged === false) return true; if (/non.?wing|wingless|no.?wing/.test(c)) return true; return false; } var L = LIGHTNING_SPRINT_BASELINE; var STG2 = 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_BUCKET5 = { 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 (${STG2.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 ${STG2.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 ${STG2.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 (${STG2.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 (${STG2.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 = normalizeSurfaceState5(trackState); const vc = opts.vehicleContext || {}; const wingless = isWingless2(vc); const factory = PRIORS_BY_BUCKET5[bucket] || PRIORS_BY_BUCKET5.unknown; let list = factory(wingless).filter((p) => !skip.has(p.lever)); const hint = resolveLightningSprintChassisHint(vc); 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 (vc.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/grassrootsDirtOvalCommonPriors.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 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(profile) { return [ "quarter_midget", "micro_sprint_600", "lightning_sprint", "outlaw_kart", "hyper_midget" ].includes(profile); } var PRIORS_BY_BUCKET6 = { 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 = normalizeSurfaceState6(trackState); const list = PRIORS_BY_BUCKET6[bucket] || PRIORS_BY_BUCKET6.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(profile) { return [ "quarter_midget", "micro_sprint_600", "lightning_sprint", "outlaw_kart" ].includes(profile); } 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 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 PRIORS_BY_BUCKET7 = { 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 = normalizeSurfaceState7(trackState); const list = PRIORS_BY_BUCKET7[bucket] || PRIORS_BY_BUCKET7.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, micro_sprint_600: MICRO600, lightning_sprint: LIGHTNING }; function grassrootsSurfaceAdjustments(trackState, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) return []; const bucket = normalizeGrassrootsSurface(trackState); if (bucket === "unknown") return []; const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const list = PROFILE_ADJUSTMENTS[profile]?.[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)" } }, 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 num2(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, profile) { 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, 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(profile, enrichedVc, trackState) { const bands = REF[profile]; 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", num2(m.stagger), staggerBand2, "rear stagger"] ]; for (const [, val, band, label] of checks) { const cmp = compareToBand(num2(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 (${profile}): 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 = num2(m.left_pct) + num2(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 (${profile}): 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 (${profile}): 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, profile) { if (!isGrassrootsDayOneProfile(profile)) 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", 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[profile] || "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, profile) { const style = enrichedVc.setup_style || enrichedVc.front_susp; if (!style) return null; const s = String(style).toLowerCase(); if (profile === "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 (${profile}): front suspension type changes which levers respond first` }; } function grassrootsContextPersonalizationPriors(profile, vc, trackState, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { return { priors: [], gapPriors: [], assessment: null }; } const enriched = enrichGrassrootsVehicleContext(vc, profile); const assessment = assessGrassrootsContext(enriched, trackState, profile); const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const priors = []; for (const p of measurementInsightPriors(profile, enriched, trackState)) { if (!skip.has(p.lever)) priors.push(p); } const style = setupStylePrior(enriched, profile); 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, profile); 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 ~${(num2(m.left_pct) + num2(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 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 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" } ] }, 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, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { return { firstMovePriors: [], guardrailPriors: [] }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeSurfaceState8(trackState); const maxFirst = opts.maxFirst ?? 2; const maxGuard = opts.maxGuard ?? 2; const firstList = FIRST_MOVES_BY_PROFILE[profile]?.[bucket] || FIRST_MOVES_BY_PROFILE[profile]?.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)" }; 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" } }, 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" }, 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, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { 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[profile]; 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 (${profile.replace(/_/g, " ")}): conservative manufacturer/community reference \u2014 not a mandate` }); } const cond = COND_BY_PROFILE[profile]?.[bucket] || COND_BY_PROFILE[profile]?.unknown; if (cond && !skip.has("tire_guide_cond") && !skip.has("surface_psi")) { priors.push(cond); } const rr = RR_MANAGE[profile]; if (rr && !skip.has("tire_guide_rr")) priors.push(rr); const priority = PSI_PRIORITY[profile]; 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" }, 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" }, 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" } }, 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" }, 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, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { 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[profile]; if (targets && !skip.has("scale_guide_targets") && !hasClassScaling) { priors.push(targets); } const effects = EFFECTS_BY_PROFILE[profile]; 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[profile]?.[bucket] || COND_BY_PROFILE2[profile]?.unknown; if (cond && !skip.has("scale_guide_cond") && !skip.has("surface_cross")) { priors.push(cond); } const priority = PRIORITY_BY_PROFILE[profile]; 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(vc = {}) { const entry = String(vc.trait_entry || "").toLowerCase(); const mid = String(vc.trait_mid || "").toLowerCase(); const exit = String(vc.trait_exit || "").toLowerCase(); const notes = String(vc.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 (/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" } }, 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" } }, 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" } }, 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" } } }; 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" } }, 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" } }, 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" } }, 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" } } }; var BUCKET_HINT = { drying: " (drying track: often less RS psi before geometry)", slick: " (slick: protect RR \u2014 stagger/psi before cross stack)", greasy: " (greasy: conservative cross/wing \u2014 one lever)", tacky: "", unknown: "" }; function grassrootsHandlingDiagnosis(trackState, profile, vc = {}, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { return { handlingPriors: [], diagnosedSymptom: null }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const enriched = enrichGrassrootsVehicleContext(vc, profile); const symptom = detectGrassrootsHandlingSymptom(enriched); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; if (!symptom) { return { handlingPriors: [], diagnosedSymptom: null }; } const fix = FIXES[profile]?.[symptom.key]; 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 priors = [{ lever: `handle_fix_${symptom.key}`, action: `${symptom.label}: ${fix.action}${hint}`, reason: `${fix.reason}${feelNote}` }]; const alt = ALT_FIXES[profile]?.[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 PHILOSOPHY4 = { 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" }, 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, profile, vc = {}, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { return { shockPriors: [], surfaceBucket: "unknown" }; } const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const bucket = normalizeGrassrootsSurface(trackState); const maxPriors = opts.maxPriors ?? 3; const enriched = enrichGrassrootsVehicleContext(vc, profile); const symptom = detectGrassrootsHandlingSymptom(enriched); const priors = []; if (!skip.has("shock_guide_philosophy")) priors.push(PHILOSOPHY4); if (!skip.has("shock_guide_when")) { priors.push(WHEN_FIRST); } const hasClassShock = skip.has("shock") || skip.has("shock_adj"); const start = START_BY_PROFILE[profile]; if (start && !skip.has("shock_guide_start") && !hasClassShock) { priors.push(start); } if (symptom) { const fine = SYMPTOM_SHOCK[profile]?.[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" }, 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, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { 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[profile]; 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" }, 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, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { 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[profile]; 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" }, 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, profile, opts = {}) { if (!profile || !isGrassrootsDayOneProfile(profile)) { 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[profile]; 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/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 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 PSI6 = HYPER_MIDGET_BASELINE.tire_psi; var STG3 = HYPER_MIDGET_BASELINE.stagger_in; var PRIORS_BY_BUCKET8 = { 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 ${STG3.start} in stagger (typical band ${STG3.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 ${STG3.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 ${STG3.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 ${STG3.start} in (${STG3.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 = normalizeSurfaceState9(trackState); const list = PRIORS_BY_BUCKET8[bucket] || PRIORS_BY_BUCKET8.unknown; return list.filter((p) => !skip.has(p.lever)); } function resolvePublicBaselineProfile(trackCondition = {}, opts = {}) { const vc = trackCondition?.vehicle_context || {}; const cls = String(vc.car_class || vc.class_name || opts.car_class || "").toLowerCase(); const carType = String(vc.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 (isMicroSprint600Profile(vc, cls, carType)) { return "micro_sprint_600"; } if (isLightningSprintProfile(vc, cls, carType)) { return "lightning_sprint"; } if (/midget/.test(cls) && !/micro|stock|quarter/.test(cls)) return "hyper_midget"; if (carType === "jrsprint" || /jr\.?\s*sprint|junior sprint/.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/.test(String(vc.chassis_name || "").toLowerCase())) return "maxim_sprint_winged"; return "hyper_midget"; } function finishGrassrootsBaseline(profile, result, trackState, skip) { if (!isGrassrootsDirtOvalProfile(profile)) { 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(profile) ? grassrootsDayOnePriors(trackState, { skipLevers: used, profile }) : []; dayOnePriors.forEach((p) => used.add(p.lever)); const surfaceBucket = isGrassrootsDayOneProfile(profile) ? normalizeGrassrootsSurface(trackState) : "unknown"; const surfacePriors = isGrassrootsDayOneProfile(profile) ? grassrootsSurfaceAdjustments(trackState, profile, { skipLevers: used }) : []; surfacePriors.forEach((p) => used.add(p.lever)); const tireBlock = isGrassrootsDayOneProfile(profile) ? grassrootsTirePressureGuidance(trackState, profile, { skipLevers: used }) : { tirePriors: [], surfaceBucket: "unknown" }; (tireBlock.tirePriors || []).forEach((p) => used.add(p.lever)); const scalingBlock = isGrassrootsDayOneProfile(profile) ? grassrootsScalingGuidance(trackState, profile, { skipLevers: used }) : { scalingPriors: [], surfaceBucket: "unknown" }; (scalingBlock.scalingPriors || []).forEach((p) => used.add(p.lever)); const handlingBlock = isGrassrootsDayOneProfile(profile) ? grassrootsHandlingDiagnosis(trackState, profile, 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(profile) ? grassrootsShockGuidance(trackState, profile, result.vehicle_context || {}, { skipLevers: used }) : { shockPriors: [], surfaceBucket: "unknown" }; (shockBlock.shockPriors || []).forEach((p) => used.add(p.lever)); const practiceBlock = isGrassrootsDayOneProfile(profile) ? grassrootsPracticeStrategy(trackState, profile, { skipLevers: used }) : { practicePriors: [], surfaceBucket: "unknown" }; (practiceBlock.practicePriors || []).forEach((p) => used.add(p.lever)); const raceBlock = isGrassrootsDayOneProfile(profile) ? grassrootsRaceNightStrategy(trackState, profile, { skipLevers: used }) : { racePriors: [], surfaceBucket: "unknown" }; (raceBlock.racePriors || []).forEach((p) => used.add(p.lever)); const loggingBlock = isGrassrootsDayOneProfile(profile) ? grassrootsLoggingGuidance(trackState, profile, { skipLevers: used }) : { loggingPriors: [] }; (loggingBlock.loggingPriors || []).forEach((p) => used.add(p.lever)); const ctxBlock = isGrassrootsDayOneProfile(profile) ? grassrootsContextPersonalizationPriors(profile, result.vehicle_context || {}, trackState, { skipLevers: used }) : { priors: [], gapPriors: [], assessment: null }; const moveBlock = isGrassrootsDayOneProfile(profile) ? grassrootsFirstMoveGuardrails(trackState, profile, { 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(profile, trackState, skip, vc, classPriorsFn, source, caveat) { const enriched = enrichGrassrootsVehicleContext(vc, profile); const chassisPriors = grassrootsChassisManufacturerPriors(profile, enriched, trackState).filter((p) => !skip.has(p.lever)); const chassisDeepCaveat = resolveGrassrootsChassisDeepCaveat(profile, enriched); const classSkip = /* @__PURE__ */ new Set([...skip, ...chassisPriors.map((p) => p.lever)]); const priors = classPriorsFn(trackState, { skipLevers: classSkip, vehicleContext: enriched }); return finishGrassrootsBaseline(profile, { priors, chassisPriors, chassisDeepCaveat, source, profile, caveat, vehicle_context: enriched }, trackState, skip); } function resolvePublicBaseline(trackState, trackCondition = {}, opts = {}) { const profile = resolvePublicBaselineProfile(trackCondition, opts); const skip = opts.skipLevers || /* @__PURE__ */ new Set(); const vc = trackCondition?.vehicle_context || {}; if (profile === "outlaw_kart") { return buildProfileBaseline(profile, trackState, skip, vc, outlawKartBaselinePriors, OUTLAW_KART_BASELINE.source, OUTLAW_PUBLIC_BASELINE_CAVEAT); } if (profile === "quarter_midget") { return buildProfileBaseline(profile, trackState, skip, vc, quarterMidgetBaselinePriors, QUARTER_MIDGET_BASELINE.source, QM_PUBLIC_BASELINE_CAVEAT); } if (profile === "micro_sprint_600") { return buildProfileBaseline(profile, trackState, skip, vc, microSprint600BaselinePriors, MICRO_SPRINT_600_BASELINE.source, MICRO_SPRINT_PUBLIC_BASELINE_CAVEAT); } if (profile === "lightning_sprint") { return buildProfileBaseline(profile, trackState, skip, vc, lightningSprintBaselinePriors, LIGHTNING_SPRINT_BASELINE.source, LIGHTNING_SPRINT_PUBLIC_BASELINE_CAVEAT); } if (profile === "maxim_sprint_winged") { return finishGrassrootsBaseline(profile, { priors: maximSprintWingedBaselinePriors(trackState, { skipLevers: skip }), chassisPriors: [], source: MAXIM_WINGED_SPRINT_BASELINE.source, profile, caveat: MAXIM_PUBLIC_BASELINE_CAVEAT, vehicle_context: enrichGrassrootsVehicleContext(vc, profile) }, trackState, skip); } return buildProfileBaseline(profile, trackState, skip, vc, hyperMidgetBaselinePriors, PUBLIC_BASELINE_SOURCE, PUBLIC_BASELINE_CAVEAT.trim()); } // 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(profile) { return isGrassrootsDayOneProfile(profile); } function resolveBaselineInjectionMode(profile, { nFind = 0, hasHigh = false, bc39Mode = false } = {}) { if (bc39Mode || !profile) return "none"; if (isGrassrootsLearningProfile(profile)) { 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/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." } }; 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", 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 (/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; } const topic = detectGrassrootsAdjustmentTopic(r); if (!topic) return null; const entry = WHY[topic]; if (!entry) return null; 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 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"; let scaffoldCount = 0; const mkCaveat = (base) => thin && base ? base + GRASSROOTS_SCAFFOLD_CAVEAT_SUFFIX : base; const canScaffold = () => !thin || scaffoldCount < GRASSROOTS_SCAFFOLD_CAP; 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, 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.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.contextPriors || []) { if (leversCovered.has(p.lever)) continue; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_personalized"], reason: p.reason + (ctx.track_state ? ` (track is ${ctx.track_state})` : ""), caveat: baseline.contextPersonalizedCaveat || p.reason, flags: { 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; recs.push({ action: p.action, lever: p.lever, basis: "prior", confidence: "LOW", refs: ["grassroots_context_gap"], reason: p.reason, caveat: baseline.contextGapCaveat || p.reason, flags: { 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); } 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 profile = !bc39Mode ? resolvePublicBaselineProfile(tc) : null; const grassrootsLearning = profile && isGrassrootsLearningProfile(profile); 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 = resolveBaselineInjectionMode(profile, { 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; if (!bc39Mode && baselineMode !== "none") { const baselineBlock = injectPublicBaselines(recs, leversCovered, ctx, tc, baselineMode); contextAssessment = baselineBlock?.contextAssessment || null; diagnosedSymptom = baselineBlock?.diagnosedSymptom || null; } if (grassrootsLearning && !bc39Mode && !nullBad) { const topFinding = report.key_findings && report.key_findings[0] || null; const aba = grassrootsAbaNudgeRec(hasFindings, topFinding); if (!leversCovered.has(aba.lever)) { recs.push(aba); leversCovered.add(aba.lever); } } recs.sort((a, b) => recWeight(b) - recWeight(a)); recs.forEach((r, i) => r.rank = i + 1); let tryFirst = null; let tryFirstReason = null; let canWait = []; let prioritizationNote = null; let actionRecommendations = recs; let processRecommendations = []; if (grassrootsLearning && !bc39Mode) { const priorBlock = applyGrassrootsPrioritization(recs, { profile, nFind, hasFindings, baselineMode, contextAssessment, diagnosedSymptom }, recWeight); tryFirst = priorBlock.tryFirst; tryFirstReason = priorBlock.tryFirstReason; canWait = priorBlock.canWait; prioritizationNote = priorBlock.prioritizationNote; actionRecommendations = priorBlock.actionRecommendations; processRecommendations = priorBlock.processRecommendations; } let nEducational = 0; if (grassrootsLearning && !bc39Mode) { ({ nEducational } = attachGrassrootsEducationalNotes(recs, { profile, 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 && !grassrootsLearning) { 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 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"); 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 (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) 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; } return { recommendations: recs, actionRecommendations, processRecommendations, tryFirst, tryFirstReason, canWait, prioritizationNote, nEducational, 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) return tryFirstRecTag(r); 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?.chassis_specific) 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"); } return __toCommonJS(browser_entry_exports); })();