plexy and noah

This commit is contained in:
firestar5683
2026-06-23 18:00:13 -05:00
parent 7e8832d672
commit 1382200cda
3 changed files with 161 additions and 88 deletions
+30 -81
View File
@@ -632,29 +632,13 @@ KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_MAX = 8.5
KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_WIDTH = 1.4
VOLT_PLEXY_LATERAL_TESTING_GROUND_ID = testing_ground.id_7
VOLT_PLEXY_FF_GAIN_LEFT = 0.12
VOLT_PLEXY_FF_GAIN_RIGHT = 0.07
VOLT_PLEXY_FF_ONSET = 0.10
VOLT_PLEXY_FF_ONSET_WIDTH = 0.06
VOLT_PLEXY_FF_CUTOFF = 1.35
VOLT_PLEXY_FF_CUTOFF_WIDTH = 0.24
VOLT_PLEXY_TRANSITION_SPEED = 10.5
VOLT_PLEXY_PHASE_SCALE = 0.10
VOLT_PLEXY_TURN_IN_BOOST_LEFT = 0.22
VOLT_PLEXY_TURN_IN_BOOST_RIGHT = 0.24
VOLT_PLEXY_UNWIND_TAPER_LEFT = 0.22
VOLT_PLEXY_UNWIND_TAPER_RIGHT = 0.56
VOLT_PLEXY_FRICTION_MULT = 1.04
VOLT_PLEXY_FRICTION_LAT_RISE = 0.22
VOLT_PLEXY_FRICTION_JERK_RISE = 0.24
VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_LEFT = 0.16
VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_RIGHT = 0.12
VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_LEFT = 0.15
VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_RIGHT = 0.34
VOLT_PLEXY_TURN_IN_FRICTION_BOOST_LEFT = 0.08
VOLT_PLEXY_TURN_IN_FRICTION_BOOST_RIGHT = 0.06
VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_LEFT = 0.16
VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_RIGHT = 0.40
VOLT_PLEXY_FF_EXTRA_MULT_LEFT = 1.10
VOLT_PLEXY_FF_EXTRA_MULT_RIGHT = 1.22
VOLT_PLEXY_FRICTION_SCALE_MULT_LEFT = 1.04
VOLT_PLEXY_FRICTION_SCALE_MULT_RIGHT = 1.08
VOLT_PLEXY_FRICTION_THRESHOLD_MULT_LEFT = 0.95
VOLT_PLEXY_FRICTION_THRESHOLD_MULT_RIGHT = 0.93
VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT = 0.80
PRIUS_TRANSITION_SPEED = 10.0
PRIUS_PHASE_SCALE = 0.09
PRIUS_FF_GAIN_LEFT = 0.12
@@ -2023,72 +2007,34 @@ def volt_plexy_lateral_testing_ground_active() -> bool:
return testing_ground.use(VOLT_PLEXY_LATERAL_TESTING_GROUND_ID)
def _volt_plexy_sigmoid(x: float) -> float:
return _sigmoid(x)
def _volt_plexy_low_speed_factor(v_ego: float) -> float:
return 1.0 / (1.0 + (max(v_ego, 0.0) / VOLT_PLEXY_TRANSITION_SPEED) ** 2)
def _volt_plexy_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
return math.tanh((desired_lateral_accel * desired_lateral_jerk) / VOLT_PLEXY_PHASE_SCALE)
def _volt_plexy_side_value(desired_lateral_accel: float, left_value: float, right_value: float) -> float:
return left_value if desired_lateral_accel >= 0.0 else right_value
def _volt_plexy_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
lat_factor = 1.0 - math.exp(-abs(desired_lateral_accel) / VOLT_PLEXY_FRICTION_LAT_RISE)
jerk_factor = 1.0 - math.exp(-abs(desired_lateral_jerk) / VOLT_PLEXY_FRICTION_JERK_RISE)
return _volt_plexy_low_speed_factor(v_ego) * lat_factor * jerk_factor
def get_volt_plexy_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float:
if desired_lateral_accel == 0.0:
return 1.0
gain = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FF_GAIN_LEFT, VOLT_PLEXY_FF_GAIN_RIGHT)
abs_lateral_accel = abs(desired_lateral_accel)
onset = _volt_plexy_sigmoid((abs_lateral_accel - VOLT_PLEXY_FF_ONSET) / VOLT_PLEXY_FF_ONSET_WIDTH)
cutoff = _volt_plexy_sigmoid((VOLT_PLEXY_FF_CUTOFF - abs_lateral_accel) / VOLT_PLEXY_FF_CUTOFF_WIDTH)
extra_scale = gain * onset * cutoff
phase = _volt_plexy_transition_phase(desired_lateral_accel, desired_lateral_jerk)
turn_in_weight = max(phase, 0.0)
unwind_weight = max(-phase, 0.0)
low_speed_factor = _volt_plexy_low_speed_factor(v_ego)
turn_in_boost = 1.0 + (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_TURN_IN_BOOST_LEFT, VOLT_PLEXY_TURN_IN_BOOST_RIGHT) *
turn_in_weight * low_speed_factor)
unwind_taper = 1.0 - (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_UNWIND_TAPER_LEFT, VOLT_PLEXY_UNWIND_TAPER_RIGHT) *
unwind_weight * (0.35 + 0.65 * low_speed_factor))
return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0))
standard_scale = get_volt_standard_ff_scale(desired_lateral_accel, desired_lateral_jerk, v_ego)
extra_scale = standard_scale - 1.0
extra_mult = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FF_EXTRA_MULT_LEFT, VOLT_PLEXY_FF_EXTRA_MULT_RIGHT)
return 1.0 + (extra_scale * extra_mult)
def get_volt_plexy_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float:
base_threshold = get_friction_threshold(v_ego)
transition_envelope = _volt_plexy_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
phase = _volt_plexy_transition_phase(desired_lateral_accel, desired_lateral_jerk)
turn_in_weight = max(phase, 0.0)
unwind_weight = max(-phase, 0.0)
threshold_scale = 1.0 - (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_LEFT, VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_RIGHT) *
transition_envelope * turn_in_weight)
threshold_scale += (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_LEFT, VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_RIGHT) *
transition_envelope * unwind_weight)
return base_threshold * min(max(threshold_scale, 0.84), 1.14)
standard_threshold = get_volt_standard_friction_threshold(v_ego, desired_lateral_accel, desired_lateral_jerk)
threshold_mult = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FRICTION_THRESHOLD_MULT_LEFT, VOLT_PLEXY_FRICTION_THRESHOLD_MULT_RIGHT)
return standard_threshold * threshold_mult
def get_volt_plexy_friction_scale(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
transition_envelope = _volt_plexy_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
phase = _volt_plexy_transition_phase(desired_lateral_accel, desired_lateral_jerk)
turn_in_weight = max(phase, 0.0)
unwind_weight = max(-phase, 0.0)
friction_scale = VOLT_PLEXY_FRICTION_MULT
friction_scale += (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_TURN_IN_FRICTION_BOOST_LEFT, VOLT_PLEXY_TURN_IN_FRICTION_BOOST_RIGHT) *
transition_envelope * turn_in_weight)
friction_scale -= (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_LEFT, VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_RIGHT) *
transition_envelope * unwind_weight)
return min(max(friction_scale, 0.90), 1.12)
standard_scale = get_volt_standard_friction_scale(v_ego, desired_lateral_accel, desired_lateral_jerk)
friction_extra = standard_scale - 1.0
friction_mult = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FRICTION_SCALE_MULT_LEFT, VOLT_PLEXY_FRICTION_SCALE_MULT_RIGHT)
return 1.0 + (friction_extra * friction_mult)
def get_volt_plexy_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
standard_scale = get_volt_standard_center_taper_scale(desired_lateral_accel, v_ego)
standard_reduction = 1.0 - standard_scale
return 1.0 - (standard_reduction * VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT)
class LatControlTorque(LatControl):
@@ -2131,7 +2077,6 @@ class LatControlTorque(LatControl):
self.is_kia_forte = CP.carFingerprint in KIA_FORTE_CARS
self.is_kia_ev6 = CP.carFingerprint in KIA_EV6_CARS
self.is_civic_bosch_modified = CP.carFingerprint == HONDA_CAR.HONDA_CIVIC_BOSCH and bool(CP.flags & HondaFlags.EPS_MODIFIED)
self.is_volt_cc = CP.carFingerprint == GM_CAR.CHEVROLET_VOLT_CC
self.is_silverado = CP.carFingerprint in SILVERADO_CARS
if self.is_ioniq_6:
self.low_speed_reset_threshold = min(self.low_speed_reset_threshold, IONIQ_6_LOW_SPEED_PID_RESET_SPEED)
@@ -2268,9 +2213,10 @@ class LatControlTorque(LatControl):
kia_niro_phev_2022_active = self.is_kia_niro_phev_2022
kia_forte_active = self.is_kia_forte
kia_ev6_test_active = self.is_kia_ev6 and kia_ev6_lateral_testing_ground_active()
volt_plexy_test_active = self.is_volt_cc and volt_plexy_lateral_testing_ground_active()
volt_plexy_test_active = self.is_volt_standard and volt_plexy_lateral_testing_ground_active()
ioniq_5_center_taper = get_ioniq_5_center_taper_scale(setpoint, CS.vEgo) if ioniq_5_active else 1.0
volt_standard_center_taper = get_volt_standard_center_taper_scale(setpoint, CS.vEgo) if volt_standard_test_active else 1.0
volt_plexy_center_taper = get_volt_plexy_center_taper_scale(setpoint, CS.vEgo) if volt_plexy_test_active else 1.0
ioniq_ev_old_center_taper = get_ioniq_ev_old_center_taper_scale(setpoint, CS.vEgo) if ioniq_ev_old_active else 1.0
ioniq_6_center_taper = get_ioniq_6_center_taper_scale(setpoint, CS.vEgo) if ioniq_6_active else 1.0
sonata_center_taper = get_sonata_center_taper_scale(setpoint, CS.vEgo) if sonata_active else 1.0
@@ -2344,9 +2290,10 @@ class LatControlTorque(LatControl):
elif self.is_silverado:
ff *= silverado_center_taper
elif volt_plexy_test_active:
ff *= get_volt_plexy_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo)
ff *= get_volt_plexy_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) * volt_plexy_center_taper
friction_threshold = get_volt_plexy_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
friction_scale = get_volt_plexy_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk)
friction_scale = 1.0 + ((friction_scale - 1.0) * volt_plexy_center_taper)
elif self.is_civic_bosch_modified:
ff *= get_civic_bosch_modified_b_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) * civic_bosch_modified_a_center_taper
friction_threshold = CIVIC_BOSCH_MODIFIED_B_FIXED_FRICTION_THRESHOLD
@@ -2379,6 +2326,8 @@ class LatControlTorque(LatControl):
output_torque, CS.vEgo)
elif volt_standard_test_active:
output_torque *= volt_standard_center_taper
elif volt_plexy_test_active:
output_torque *= volt_plexy_center_taper
elif kia_ev6_test_active:
output_torque *= kia_ev6_low_speed_center_taper
elif self.is_silverado:
@@ -207,19 +207,42 @@
color: var(--warning-bg);
}
.mm-chip-user-favorite {
background: rgba(142, 108, 211, 0.18);
border-color: rgba(142, 108, 211, 0.55);
color: var(--accent-color);
}
.mm-chip-warning {
background: rgba(224, 85, 119, 0.12);
border-color: rgba(224, 85, 119, 0.45);
color: var(--danger-fg);
}
.mm-icon-btn,
.mm-star {
background: transparent;
border: none;
color: #e0b45a;
border: 1px solid var(--sidebar-border-color);
border-radius: var(--border-radius-base);
color: var(--text-muted);
display: inline-flex;
font-size: 1rem;
justify-content: center;
line-height: 1;
padding: 0;
min-height: 2.2rem;
min-width: 2.45rem;
padding: 0.45rem;
}
.mm-icon-btn:hover:not(:disabled) {
background: var(--secondary-bg);
color: var(--text-color);
}
.mm-icon-btn.is-active {
background: rgba(142, 108, 211, 0.18);
border-color: rgba(142, 108, 211, 0.55);
color: var(--accent-color);
}
.mm-empty {
@@ -238,7 +261,12 @@
}
.mm-btn {
width: 100%;
flex: 1 1 auto;
width: auto;
}
.mm-icon-btn {
flex: 0 0 2.6rem;
}
.mm-search {
@@ -7,6 +7,7 @@ const state = reactive({
actionBusy: false,
sortMode: "alphabetical",
communityFavoriteFilter: "all",
userFavoriteFilter: "all",
models: [],
currentModel: "",
summary: { installed: 0, missing: 0, total: 0 },
@@ -91,6 +92,12 @@ function modelSortCompare(a, b) {
function getFilteredModels() {
let rows = [...state.models].filter(model => model && typeof model === "object");
if (state.userFavoriteFilter === "yes") {
rows = rows.filter(model => !!model.userFavorite);
} else if (state.userFavoriteFilter === "no") {
rows = rows.filter(model => !model.userFavorite);
}
if (state.communityFavoriteFilter === "yes") {
rows = rows.filter(model => !!model.communityFavorite);
} else if (state.communityFavoriteFilter === "no") {
@@ -143,8 +150,15 @@ function getReleaseOrderedModels() {
}
function getInstalledModels() {
const rows = state.sortMode === "release_date" ? getReleaseOrderedModels() : getVisibleModels();
return rows.filter(model => !!model.installed);
return state.models
.filter(model => model && typeof model === "object" && !!model.installed)
.sort(modelSortCompare);
}
function getUserFavoriteModels(installedOnly = false) {
const rows = state.models.filter(model => model && typeof model === "object" && !!model.userFavorite);
const filtered = installedOnly ? rows.filter(model => !!model.installed) : rows;
return filtered.sort(modelSortCompare);
}
function getCurrentModelName() {
@@ -317,6 +331,28 @@ async function deleteModel(modelKey) {
notify(payload.message || `Deleted files for "${modelKey}".`);
}
async function setUserFavorite(modelKey, shouldFavorite) {
const key = safeText(modelKey, "");
if (!key) return;
const favorites = getUserFavoriteModels(false)
.map(model => safeText(model.value, ""))
.filter(Boolean)
.filter(value => value !== key);
if (shouldFavorite) {
favorites.push(key);
}
const payload = await fetchJson("/api/models/preferences", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userFavorites: favorites }),
});
notify(payload.message || (shouldFavorite ? "Model added to your favorites." : "Model removed from your favorites."));
}
async function refreshManifest() {
const payload = await fetchJson("/api/models/refresh_manifest", { method: "POST" });
notify(payload.message || "Model manifest refreshed.");
@@ -336,7 +372,8 @@ async function runAction(action, modelKey = "") {
return;
}
if (state.status.isOnroad && action !== "refresh") {
const allowedOnroadActions = new Set(["refresh", "favorite", "unfavorite"]);
if (state.status.isOnroad && !allowedOnroadActions.has(action)) {
notify("Actions are blocked while onroad.", "error");
return;
}
@@ -356,6 +393,12 @@ async function runAction(action, modelKey = "") {
const confirmed = window.confirm(`Delete local files for model \"${modelKey}\"?`);
if (!confirmed) return;
await deleteModel(modelKey);
} else if (action === "favorite") {
if (!modelKey) return;
await setUserFavorite(modelKey, true);
} else if (action === "unfavorite") {
if (!modelKey) return;
await setUserFavorite(modelKey, false);
}
await fetchStatus();
@@ -397,6 +440,14 @@ function bindDomHandlers() {
return;
}
if (target.id === "mm-favorite-model-select") {
const modelKey = safeText(target.value, "");
if (!modelKey) return;
runAction("select", modelKey).catch(() => {});
target.value = "";
return;
}
if (target.id === "mm-sort-mode-select") {
const value = safeText(target.value, "alphabetical");
state.sortMode = value === "release_date" ? "release_date" : "alphabetical";
@@ -411,6 +462,15 @@ function bindDomHandlers() {
state.communityFavoriteFilter = "all";
}
}
if (target.id === "mm-user-filter-select") {
const value = safeText(target.value, "all");
if (value === "yes" || value === "no" || value === "all") {
state.userFavoriteFilter = value;
} else {
state.userFavoriteFilter = "all";
}
}
});
}
@@ -444,6 +504,8 @@ function renderActions(model) {
function renderModelRow(model) {
const label = safeText(model.label, safeText(model.value, "Unnamed"));
const key = safeText(model.value, "");
const favoriteAction = model.userFavorite ? "unfavorite" : "favorite";
const favoriteTitle = model.userFavorite ? "Remove from your favorites" : "Add to your favorites";
return html`
<div class="mm-row">
@@ -457,11 +519,20 @@ function renderModelRow(model) {
${state.sortMode === "release_date" ? "" : model.series ? html`<span class="mm-chip">${safeText(model.series)}</span>` : ""}
${model.version ? html`<span class="mm-chip">Version ${safeText(model.version)}</span>` : ""}
${model.released ? html`<span class="mm-chip">Released ${safeText(model.released)}</span>` : ""}
${model.userFavorite ? html`<span class="mm-chip mm-chip-user-favorite">Your Favorite</span>` : ""}
${model.communityFavorite ? html`<span class="mm-chip mm-chip-favorite">Community Favorite</span>` : ""}
${model.partial ? html`<span class="mm-chip mm-chip-warning">Partial Files</span>` : ""}
</div>
</div>
<div class="mm-row-actions">
<button
class="mm-icon-btn ${model.userFavorite ? "is-active" : ""}"
data-mm-action="${favoriteAction}"
data-model="${key}"
title="${favoriteTitle}"
aria-label="${favoriteTitle}">
<i class="bi ${model.userFavorite ? "bi-star-fill" : "bi-star"}"></i>
</button>
${renderActions(model)}
</div>
</div>
@@ -525,6 +596,7 @@ export function ModelManager() {
<div class="mm-status">
<span class="mm-chip">Current: ${getCurrentModelName()}</span>
<span class="mm-chip">Progress: ${safeText(state.status.progress, "Idle")}</span>
<span class="mm-chip">${() => getUserFavoriteModels(false).length} personal favorites</span>
${() => state.status.isOnroad ? html`<span class="mm-chip mm-chip-warning">Onroad: actions disabled</span>` : ""}
</div>
@@ -548,6 +620,23 @@ export function ModelManager() {
})()}
</select>
<label class="mm-filter-label" for="mm-favorite-model-select">Favorite Models</label>
<select class="mm-select" id="mm-favorite-model-select" disabled="${() => getUserFavoriteModels(true).length === 0}">
${(() => {
const favorites = getUserFavoriteModels(true);
return favorites.length > 0
? html`
<option value="">Choose a favorite</option>
${favorites.map(model => html`
<option value="${safeText(model.value)}">
${safeText(model.label, model.value)}
</option>
`)}
`
: html`<option value="">No installed favorites</option>`;
})()}
</select>
<label class="mm-filter-label" for="mm-sort-mode-select">Sort</label>
<select class="mm-select" id="mm-sort-mode-select">
<option value="alphabetical" selected="${() => state.sortMode === "alphabetical" || false}">Alphabetical</option>
@@ -556,6 +645,13 @@ export function ModelManager() {
<div class="mm-filter-break"></div>
<label class="mm-filter-label" for="mm-user-filter-select">Your Favorite</label>
<select class="mm-select" id="mm-user-filter-select">
<option value="all" selected="${() => state.userFavoriteFilter === "all" || false}">All</option>
<option value="yes" selected="${() => state.userFavoriteFilter === "yes" || false}">Yes</option>
<option value="no" selected="${() => state.userFavoriteFilter === "no"}">No</option>
</select>
<label class="mm-filter-label" for="mm-community-filter-select">Community Favorite</label>
<select class="mm-select" id="mm-community-filter-select">
<option value="all" selected="${() => state.communityFavoriteFilter === "all" || false}">All</option>