Generated by Claude Opus 5

1 Scope

This note isolates the effect of longitudinal torque distribution on vehicle behaviour at the friction limit. Tyre compound, surface friction, electronic stability control, speed, radius, mass and driver input are held fixed.

The motivating scenario is a right-hand curve at roughly \(0^\circ\)C in which the right front tyre encounters a patch of ice.

Two caveats on the ceteris paribus, both load-bearing.

Mass is not independent of drivetrain. A second motor adds roughly 60–100 kg that cannot be declined, and it shifts the centre of gravity. What follows is a partial derivative with a known confound, not a clean experiment.

The obvious objective function is the wrong one. Sections 3–4 develop the narrow metric “lateral capacity retained at the compromised tyre.” Taken alone that metric concludes put all torque at the rear — but only because the scenario stipulates the compromised tyre is at the front, and because the metric is blind to the fact that front and rear saturation have qualitatively different consequences. Section 7 corrects this. The narrow result is scenario-conditional and does not generalise.

2 Symbol glossary

2.1 Indices

Symbol Meaning Type
\(i\) tyre index, \(i \in \{\mathrm{fl}, \mathrm{fr}, \mathrm{rl}, \mathrm{rr}\}\) index (front/rear \(\times\) left/right)
\(x, y, z\) axis labels in the tyre’s contact-patch frame: \(x\) longitudinal (forward), \(y\) lateral, \(z\) vertical subscript labels, not variables

2.2 Contact forces

\(F_x\), \(F_y\) and \(F_z\) are not functions. They are the three scalar Cartesian components of the contact force vector \(\mathbf{F}_i = (F_{x,i},\, F_{y,i},\, F_{z,i})\) at tyre \(i\). Where the tyre index is dropped, read \(i = \mathrm{fr}\) — the right front, the wheel on the ice.

Symbol Meaning Units Sign convention
\(F_{x,i}\) longitudinal force at tyre \(i\) N \(> 0\) tractive, \(< 0\) braking or regen
\(F_{y,i}\) lateral (cornering) force at tyre \(i\) N \(> 0\) toward turn centre
\(F_{z,i}\) vertical load carried by tyre \(i\) N always \(\ge 0\)
\(F_x^{\text{tot}}\) total longitudinal force at the vehicle, summed over four tyres N as above

2.3 Surface and capacity

Symbol Meaning Units
\(\mu\) Coulomb friction coefficient: the ratio of maximum tangential force to normal load for a given tyre–road pair. Not a property of either surface alone. Dry asphalt \(\approx 0.8\)–\(1.0\); wet \(\approx 0.5\); ice near \(0^\circ\)C \(\approx 0.05\)–\(0.15\) dimensionless
\(C_i\) friction budget at tyre \(i\): the radius of its friction circle, \(C_i = \mu_i F_{z,i}\) N

2.4 Demand, split and loss

Symbol Meaning Range
\(\varphi\) front axle’s share of \(F_x^{\text{tot}}\). Front delivers \(\varphi F_x^{\text{tot}}\), rear delivers \((1 - \varphi) F_x^{\text{tot}}\). FWD \(\Rightarrow \varphi = 1\); RWD \(\Rightarrow \varphi = 0\); symmetric AWD \(\Rightarrow \varphi = 0.5\) \([0, 1]\)
\(\lambda\) normalised longitudinal demand: the counterfactual FWD per-front-tyre demand as a fraction of that tyre’s budget \([0, \infty)\)
\(D\) total normalised longitudinal demand, allocated across axles \([0, \infty)\)
\(\eta\) deceleration under lift-off regeneration, expressed in \(g\) typically \(0.1\)–\(0.3\)
\(\ell(\lambda)\) capacity loss at a tyre, \(\ell(\lambda) = 1 - \sqrt{1 - \min(\lambda, 1)^2}\). Convex increasing on \([0, 1]\) \([0, 1]\)
\(w_A\) weight on mechanism (A), front-axle budget competition \(> 0\)
\(w_B\) weight on mechanisms (B)+(C), rear-axle yaw instability \(> 0\)
\(k\) oversteer penalty, \(k = w_B / w_A\) \(\ge 1\)
\(\varphi^*\) expected-loss-minimising front share \([0, 1]\)

2.5 Vehicle and control

Symbol Meaning Units
\(m\) vehicle mass kg
\(g\) gravitational acceleration, \(9.81\) m/s\(^2\)
\(v\) forward speed m/s
\(R_c\) curve radius m
\(a_y\) lateral acceleration, \(a_y = v^2 / R_c\) m/s\(^2\)
\(h\) centre-of-gravity height m
\(t\) track width m
\(\alpha_r\) rear-axle slip angle rad
\(\tau\) drivetrain torque-reallocation latency s
\(\tau_r\) rotor time constant of an induction machine, \(\tau_r = L_r / R_r\) s
\(\hat{\mu}\) controller’s running estimate of surface friction dimensionless

3 The friction-circle relations

3.1 Friction circle (the constraint)

Longitudinal and lateral force compete for one budget:

\[\sqrt{F_{x,i}^2 + F_{y,i}^2} \;\le\; C_i \;=\; \mu_i F_{z,i}\]

3.2 Normalised demand (the definition)

\[\lambda \;\equiv\; \frac{F_x^{\text{tot}}}{2\,C_{\mathrm{fr}}} \qquad\Longrightarrow\qquad F_{x,\mathrm{fr}} \;=\; \varphi\,\frac{F_x^{\text{tot}}}{2} \;=\; \varphi\lambda\,C_{\mathrm{fr}}\]

So \(\lambda = 1\) means an FWD car’s front tyres would be exactly saturated by longitudinal force alone, leaving zero cornering force.

3.3 Retained lateral capacity (the result)

\[F_{y,\mathrm{fr}}^{\max}(\varphi, \lambda) \;=\; C_{\mathrm{fr}}\sqrt{1 - (\varphi\lambda)^2}, \qquad \varphi\lambda \le 1\]

and \(0\) for \(\varphi\lambda > 1\): the tyre is saturated, sliding, and contributing no cornering force at all.

3.4 The gain function

The relative improvement of a given split over FWD, everything else fixed:

\[G(\varphi, \lambda) \;=\; \frac{F_{y}^{\max}(\varphi, \lambda)}{F_{y}^{\max}(1, \lambda)} - 1 \;=\; \sqrt{\frac{1 - (\varphi\lambda)^2}{1 - \lambda^2}} - 1\]

4 The loss function and optimal allocation

4.1 Convexity

Define the capacity foregone at a tyre:

\[\ell(\lambda) \;=\; 1 - \sqrt{1 - \min(\lambda, 1)^2}\]

\(\ell\) is increasing and convex on \([0, 1]\), with a hard wall at \(\lambda = 1\). Its small-argument expansion is

\[\ell(\lambda) \;=\; \tfrac{1}{2}\lambda^2 + \tfrac{1}{8}\lambda^4 + O(\lambda^6)\]

4.2 Jensen: why equal splitting is optimal under uncertainty

For a fixed total demand \(D\) allocated across axles, convexity of \(\ell\) gives

\[2\,\ell\!\left(\tfrac{D}{2}\right) \;\le\; \ell(D) + \ell(0) \;=\; \ell(D)\]

so summed loss is minimised by equalising demand. This is the formal content of the intuition that a symmetric split is robust when the location of the compromised tyre is unknown: convexity of the loss is a sufficient condition for equal splitting to be optimal under uncertainty.

The refinement matters. \(\lambda_i\) is normalised by the local budget \(C_i = \mu_i F_{z,i}\), and those budgets differ across tyres because of load transfer. Equalising \(\lambda_i\) therefore means allocating force proportional to \(\mu_i F_{z,i}\), not equally. A 50:50 split is optimal only under the prior that all four budgets are equal — it is the maximum-entropy default, not the true optimum. Load-proportional allocation is the target a well-designed controller should pursue.

5 Implementation

lateral_capacity <- function(phi, lambda) {
  saturation <- pmin(phi * lambda, 1)
  sqrt(1 - saturation^2)
}

capacity_loss <- function(lambda) {
  1 - lateral_capacity(phi = 1, lambda = lambda)
}

gain_over_fwd <- function(phi, lambda) {
  fwd_capacity <- lateral_capacity(phi = 1, lambda = lambda)
  if_else(
    fwd_capacity > 0,
    lateral_capacity(phi, lambda) / fwd_capacity - 1,
    NA_real_
  )
}

expected_loss <- function(phi, lambda, oversteer_penalty) {
  capacity_loss(phi * lambda) +
    oversteer_penalty * capacity_loss((1 - phi) * lambda)
}

optimal_front_share <- function(lambda,
                                oversteer_penalty,
                                grid = seq(0, 1, by = 0.0005)) {
  losses <- expected_loss(grid, lambda, oversteer_penalty)
  grid[which.min(losses)]
}

optimal_front_share_small_lambda <- function(oversteer_penalty) {
  oversteer_penalty / (1 + oversteer_penalty)
}

split_colours <- c(
  "#e34948", "#eb6834", "#eda100", "#1baf7a", "#2a78d6"
)

theme_note <- function() {
  theme_minimal(base_size = 11) +
    theme(
      panel.grid.minor = element_blank(),
      legend.title = element_text(size = 10),
      legend.key.width = unit(1.6, "lines")
    )
}

6 Figure 1: the friction circle

Both panels show the same tyre with the same budget \(C\); only the longitudinal demand differs.

circle_path <- tibble(
  theta = seq(0, 2 * pi, length.out = 361),
  fy = cos(theta),
  fx = sin(theta)
)

operating_vectors <- tibble(
  split = factor(
    c("FWD (phi = 1.0)", "AWD (phi = 0.5)"),
    levels = c("FWD (phi = 1.0)", "AWD (phi = 0.5)")
  ),
  fx = c(0.70, 0.35)
) |>
  mutate(fy = sqrt(1 - fx^2))

ggplot() +
  geom_path(
    data = circle_path,
    aes(x = fy, y = fx),
    colour = "grey65",
    linewidth = 0.4
  ) +
  geom_segment(
    data = operating_vectors,
    aes(x = 0, y = 0, xend = 0, yend = fx),
    colour = "#d85a30",
    linewidth = 1.2
  ) +
  geom_segment(
    data = operating_vectors,
    aes(x = 0, y = fx, xend = fy, yend = fx),
    colour = "#1d9e75",
    linewidth = 1.2
  ) +
  geom_hline(yintercept = 0, colour = "grey85", linewidth = 0.3) +
  geom_vline(xintercept = 0, colour = "grey85", linewidth = 0.3) +
  facet_wrap(vars(split)) +
  coord_fixed(xlim = c(-1.05, 1.05), ylim = c(-1.05, 1.05)) +
  labs(
    x = expression(F[y] / C ~ "  (lateral)"),
    y = expression(F[x] / C ~ "  (longitudinal)"),
    caption = paste(
      "Orange: longitudinal demand.",
      "Green: lateral capacity remaining."
    )
  ) +
  theme_note()

Halving the front axle’s longitudinal duty takes the tyre from roughly 71% to 94% of its lateral limit. AWD does not add grip; it stops the front axle from spending grip on propulsion.

7 Figure 2: retained capacity across the demand range

phi_levels <- c(1, 0.9, 0.5, 0.37, 0)

phi_labels <- c("1.00  (FWD)", "0.90", "0.50", "0.37", "0.00  (RWD)")

capacity_grid <- expand_grid(
  phi = phi_levels,
  lambda = seq(0, 2, by = 0.005)
) |>
  mutate(
    capacity = lateral_capacity(phi, lambda),
    phi_label = factor(phi, levels = phi_levels, labels = phi_labels)
  )

ggplot(
  capacity_grid,
  aes(x = lambda, y = capacity, colour = phi_label, linetype = phi_label)
) +
  geom_line(linewidth = 0.7) +
  scale_colour_manual(values = set_names(split_colours, phi_labels)) +
  scale_linetype_manual(
    values = c("solid", "longdash", "dotted", "dotdash", "dashed")
  ) +
  scale_x_continuous(breaks = seq(0, 2, by = 0.25), expand = c(0, 0)) +
  scale_y_continuous(limits = c(0, 1.02), expand = c(0, 0)) +
  labs(
    x = expression(lambda ~ "  (normalised longitudinal demand)"),
    y = expression(F[y]^max / C),
    colour = expression(varphi),
    linetype = expression(varphi)
  ) +
  theme_note()

Each curve is a quarter-ellipse in disguise: \(\varphi\) rescales the \(\lambda\) axis, so the FWD curve reaches zero at \(\lambda = 1\) while the \(\varphi = 0.5\) curve survives to \(\lambda = 2\). The \(\varphi = 0\) line is flat at 1 — a tyre asked to do no longitudinal work retains its full budget for cornering regardless of what the rest of the car is doing.

8 Figure 3: the gain function

gain_levels <- c(0.9, 0.5, 0.37, 0)

gain_labels <- c(
  "0.90  (EX90, normal)",
  "0.50  (Solterra)",
  "0.37  (EX30 Twin)",
  "0.00  (MEB, cruise)"
)

gain_grid <- expand_grid(
  phi = gain_levels,
  lambda = seq(0, 0.99, by = 0.002)
) |>
  mutate(
    gain = gain_over_fwd(phi, lambda),
    phi_label = factor(phi, levels = gain_levels, labels = gain_labels)
  )

ggplot(
  gain_grid,
  aes(x = lambda, y = gain, colour = phi_label, linetype = phi_label)
) +
  geom_line(linewidth = 0.7) +
  scale_colour_manual(values = set_names(split_colours[-1], gain_labels)) +
  scale_linetype_manual(values = c("longdash", "dotted", "dotdash", "dashed")) +
  scale_x_continuous(breaks = seq(0, 1, by = 0.1), expand = c(0, 0)) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(0, 2.6)) +
  labs(
    x = expression(lambda ~ "  (normalised longitudinal demand)"),
    y = "G   (extra cornering force vs FWD)",
    colour = expression(varphi),
    linetype = expression(varphi)
  ) +
  theme_note()

\(G\) has a vertical asymptote at \(\lambda = 1\) for every \(\varphi < 1\): as the FWD denominator \(\sqrt{1 - \lambda^2} \to 0\), the ratio diverges. This is the formal statement of “AWD does almost nothing until it does everything.” \(\partial G / \partial \lambda \approx 0\) below \(\lambda \approx 0.4\); above \(\lambda \approx 0.8\) the curve is nearly vertical.

9 Three mechanisms, not one

Sections 3–4 evaluate the objective at the front axle only. That is blind to the asymmetry in consequences:

  • Front saturation \(\rightarrow\) understeer. Yaw rate falls below commanded, the car runs wide, \(R_c\) increases, \(a_y = v^2 / R_c\) drops, and the front tyres recover. Self-limiting: the plant returns toward the limit unaided, and the corrective input is intuitive.
  • Rear saturation \(\rightarrow\) oversteer. Yaw rate exceeds commanded, which increases \(\alpha_r\), which reduces rear lateral force, which increases yaw rate further. Positive feedback, divergent, requiring an active and counterintuitive correction inside a short window.

So there are three distinct mechanisms, and they do not share a sign:

Mechanism Favours
(A) Friction-budget competition at the steering axle rear drive
(B) Asymmetric loss: understeer recoverable, oversteer divergent front drive
(C) Point of application of the tractive force relative to the CoG front drive

Mechanism (C) is separate from (B) and holds independently of whether any tyre is saturated. A tractive force applied ahead of the CoG rotates with the steered wheels and generates a moment opposing any yaw perturbation. Applied behind the CoG it stays aligned with the body and, once the body yaws, acquires a lateral component that amplifies the yaw. This is why RWD vehicles spin under power and FWD vehicles essentially do not.

Two of the three favour the front. The Section 3–4 conclusion that rear bias is preferable holds conditional on the ice being under a front wheel. As a general prior it does not survive.

10 Optimising the split

10.1 The objective

\[\mathbb{E}[L](\varphi) \;=\; w_A\,\ell(\varphi\lambda) \;+\; w_B\,\ell\big((1-\varphi)\lambda\big), \qquad k \equiv \frac{w_B}{w_A} \ge 1\]

Opposing gradients with convex terms give a convex sum, hence an interior minimum.

10.2 Closed form in the small-\(\lambda\) limit

Substituting \(\ell(x) \approx x^2/2\):

\[\mathbb{E}[L](\varphi) \;\approx\; \frac{\lambda^2}{2} \left[\varphi^2 + k\,(1-\varphi)^2\right]\]

\[\frac{\partial}{\partial \varphi}: \quad 2\varphi - 2k(1-\varphi) = 0 \quad\Longrightarrow\quad \boxed{\;\varphi^* = \frac{k}{1+k}\;} \quad\Longleftrightarrow\quad \frac{\varphi^*}{1-\varphi^*} = k\]

The optimal front:rear torque ratio equals the rear:front loss ratio, and in this limit it is independent of \(\lambda\). With no oversteer penalty (\(k = 1\)) it returns \(\varphi^* = 0.5\), recovering the Jensen result. As \(k \to \infty\) it tends to pure FWD. Saturation effects at high \(\lambda\) pull \(\varphi^*\) below the closed form, as Figure 6 shows.

10.3 Figure 4: convexity and the Jensen construction

demand_total <- 0.8

loss_curve <- tibble(lambda = seq(0, 1, by = 0.002)) |>
  mutate(loss = capacity_loss(lambda))

chord <- tibble(
  lambda = c(0, demand_total),
  loss = capacity_loss(c(0, demand_total))
)

jensen_gap <- tibble(
  lambda = demand_total / 2,
  curve_value = capacity_loss(demand_total / 2),
  chord_value = capacity_loss(demand_total) / 2
)

ggplot() +
  geom_line(
    data = loss_curve,
    aes(x = lambda, y = loss),
    colour = "#4a3aa7",
    linewidth = 0.8
  ) +
  geom_line(
    data = chord,
    aes(x = lambda, y = loss),
    colour = "#eb6834",
    linetype = "dashed",
    linewidth = 0.7
  ) +
  geom_segment(
    data = jensen_gap,
    aes(x = lambda, xend = lambda, y = curve_value, yend = chord_value),
    colour = "#1baf7a",
    linewidth = 1.1
  ) +
  geom_point(
    data = jensen_gap,
    aes(x = lambda, y = curve_value),
    colour = "#4a3aa7",
    size = 2.2
  ) +
  geom_point(
    data = jensen_gap,
    aes(x = lambda, y = chord_value),
    colour = "#eb6834",
    size = 2.2
  ) +
  scale_x_continuous(breaks = seq(0, 1, by = 0.2)) +
  labs(
    x = expression(lambda),
    y = expression(l(lambda) ~ "  (capacity foregone)"),
    caption = paste(
      "Purple: the loss function. Orange dashed: chord from 0 to D.",
      "\nGreen: the Jensen gap at D/2 — twice this is the saving from",
      "splitting."
    )
  ) +
  theme_note()

tibble(
  allocation = c("Concentrated (FWD)", "Symmetric (50:50)"),
  front_demand = c(demand_total, demand_total / 2),
  rear_demand = c(0, demand_total / 2)
) |>
  mutate(
    summed_loss = capacity_loss(front_demand) + capacity_loss(rear_demand)
  ) |>
  mutate(across(where(is.numeric), \(x) round(x, 4))) |>
  knitr::kable(
    caption = paste0(
      "Summed loss at equal total demand D = ", demand_total,
      ". Convexity guarantees the split is never worse."
    )
  )
Summed loss at equal total demand D = 0.8. Convexity guarantees the split is never worse.
allocation front_demand rear_demand summed_loss
Concentrated (FWD) 0.8 0.0 0.400
Symmetric (50:50) 0.4 0.4 0.167

10.4 Figure 5: expected loss against the front share

Weights are stipulated, not measured. Treat the shape as the result, not the location of the argmin.

reference_lambda <- 0.8
reference_penalty <- 2.5

loss_decomposition <- tibble(phi = seq(0, 1, by = 0.002)) |>
  mutate(
    front_budget = capacity_loss(phi * reference_lambda),
    rear_instability = reference_penalty *
      capacity_loss((1 - phi) * reference_lambda),
    total = front_budget + rear_instability
  ) |>
  pivot_longer(
    cols = c(front_budget, rear_instability, total),
    names_to = "component",
    values_to = "loss"
  ) |>
  mutate(
    component = factor(
      component,
      levels = c("front_budget", "rear_instability", "total"),
      labels = c(
        "(A) front budget competition",
        "(B)+(C) rear yaw instability",
        "total expected loss"
      )
    )
  )

phi_star <- optimal_front_share(reference_lambda, reference_penalty)

minimum_point <- tibble(
  phi = phi_star,
  loss = expected_loss(phi_star, reference_lambda, reference_penalty)
)

ggplot(loss_decomposition, aes(x = phi, y = loss)) +
  geom_line(aes(colour = component, linetype = component), linewidth = 0.8) +
  geom_point(data = minimum_point, colour = "#4a3aa7", size = 2.6) +
  scale_colour_manual(values = c("#2a78d6", "#eb6834", "#4a3aa7")) +
  scale_linetype_manual(values = c("dashed", "longdash", "solid")) +
  scale_x_continuous(breaks = seq(0, 1, by = 0.1), expand = c(0, 0)) +
  labs(
    x = expression(varphi ~ "  (front share of longitudinal force)"),
    y = "relative loss (arbitrary units)",
    colour = NULL,
    linetype = NULL
  ) +
  theme_note() +
  theme(legend.position = "bottom")

At \(\lambda = 0.8\) and \(k = 2.5\) the grid search gives \(\varphi^* = 0.684\), against a closed-form prediction of \(0.714\). The optimum is interior and front-biased.

10.5 Figure 6: sensitivity of \(\varphi^*\) to the oversteer penalty

penalty_grid <- expand_grid(
  lambda = c(0.4, 0.6, 0.8, 0.95),
  oversteer_penalty = seq(1, 4, by = 0.02)
) |>
  mutate(
    phi_star = map2_dbl(lambda, oversteer_penalty, optimal_front_share),
    lambda_label = factor(
      lambda,
      levels = c(0.4, 0.6, 0.8, 0.95),
      labels = c("0.40", "0.60", "0.80", "0.95")
    )
  )

closed_form <- tibble(oversteer_penalty = seq(1, 4, by = 0.02)) |>
  mutate(phi_star = optimal_front_share_small_lambda(oversteer_penalty))

ggplot(penalty_grid, aes(x = oversteer_penalty, y = phi_star)) +
  geom_line(
    data = closed_form,
    colour = "grey45",
    linetype = "dashed",
    linewidth = 0.7
  ) +
  geom_line(aes(colour = lambda_label), linewidth = 0.8) +
  geom_hline(yintercept = 0.5, colour = "grey80", linewidth = 0.3) +
  scale_colour_manual(values = split_colours[c(1, 3, 4, 5)]) +
  scale_x_continuous(breaks = seq(1, 4, by = 0.5), expand = c(0, 0)) +
  scale_y_continuous(limits = c(0.45, 0.85)) +
  labs(
    x = expression(k ~ "  (oversteer penalty, " * w[B] / w[A] * ")"),
    y = expression(varphi * "*"),
    colour = expression(lambda),
    caption = "Grey dashed: closed form k / (1 + k)."
  ) +
  theme_note()

The closed form is a good approximation at low \(\lambda\) and an upper bound throughout: as the front saturation wall at \(\varphi\lambda = 1\) approaches, \(\varphi^*\) is pulled down.

This has an external validation. Mass-market Haldex-based AWD systems default to roughly 90:10 or 95:5 front. Audi’s Torsen quattro ran 40:60 rear-biased — and was sold on handling, not safety. The safety-optimised industry answer has consistently been front-biased AWD, which is what this decomposition predicts.

It also reframes Subaru. A 50:50 manual centre diff, or 45:55 in the VTD automatics, sits rear of the safety optimum. That is a traction-and-balance calibration consistent with the rally lineage, not a stability-maximising one.

11 Locating the operating points

Lateral load transfer (quasi-static, ignoring roll-stiffness distribution between axles):

\[\Delta F_z = \frac{m\,a_y\,h}{t}, \qquad F_{z,\mathrm{fr}} = \frac{mg}{4} - \frac{\Delta F_z}{2}\]

Regeneration demand: \(\;F_x^{\text{tot}} = -\eta\,m g\)

g_const <- 9.81

vehicle <- list(
  mass_kg = 2000,
  cog_height_m = 0.62,
  track_width_m = 1.58,
  mu_ice = 0.10,
  lateral_accel_g = 0.30
)

lateral_load_transfer_n <-
  vehicle$mass_kg * vehicle$lateral_accel_g * g_const *
    vehicle$cog_height_m / vehicle$track_width_m

static_corner_load_n <- vehicle$mass_kg * g_const / 4

inside_front_load_n <- static_corner_load_n - lateral_load_transfer_n / 2

friction_budget_n <- vehicle$mu_ice * inside_front_load_n

tibble(
  quantity = c(
    "Lateral load transfer (N)",
    "Static corner load (N)",
    "Inside front load F_z,fr (N)",
    "Friction budget C_fr (N)"
  ),
  value = round(
    c(
      lateral_load_transfer_n,
      static_corner_load_n,
      inside_front_load_n,
      friction_budget_n
    ),
    1
  )
) |>
  knitr::kable(caption = "Derived quantities for the reference vehicle.")
Derived quantities for the reference vehicle.
quantity value
Lateral load transfer (N) 2309.7
Static corner load (N) 4905.0
Inside front load F_z,fr (N) 3750.2
Friction budget C_fr (N) 375.0
operating_points <- tibble(
  condition = c(
    "Coasting, regen off",
    "Steady 80 km/h",
    "Lift-off regen, eta = 0.05",
    "Lift-off regen, eta = 0.15"
  ),
  fx_total_n = c(
    0,
    450,
    0.05 * vehicle$mass_kg * g_const,
    0.15 * vehicle$mass_kg * g_const
  )
) |>
  mutate(
    lambda = fx_total_n / (2 * friction_budget_n),
    fwd = lateral_capacity(phi = 1, lambda = lambda),
    awd_symmetric = lateral_capacity(phi = 0.5, lambda = lambda)
  )

operating_points |>
  mutate(across(c(fx_total_n, lambda, fwd, awd_symmetric), \(x) round(x, 2))) |>
  knitr::kable(
    col.names = c(
      "Condition", "F_x total (N)", "lambda",
      "F_y/C at phi = 1", "F_y/C at phi = 0.5"
    ),
    caption = "Operating points on the low-friction patch."
  )
Operating points on the low-friction patch.
Condition F_x total (N) lambda F_y/C at phi = 1 F_y/C at phi = 0.5
Coasting, regen off 0 0.00 1.0 1.00
Steady 80 km/h 450 0.60 0.8 0.95
Lift-off regen, eta = 0.05 981 1.31 0.0 0.76
Lift-off regen, eta = 0.15 2943 3.92 0.0 0.00

The friction budget of that tyre is roughly 375 N — about the weight of a large dog. Steady cruise already consumes a substantial fraction of it, and any meaningful regeneration saturates it outright. At the highest \(\lambda\) in the table, halving \(\varphi\) still leaves \(\varphi\lambda > 1\): neither drivetrain saves you, only reducing \(F_x^{\text{tot}}\) does.

12 Latency and the endogeneity of \(\varphi\)

Treating \(\varphi\) as a constant understates a dual-motor AWD system. In a two-eAxle EV, \(\varphi\) is a control output spanning the full \([0, 1]\) range, switchable in roughly 10–20 ms. In principle such a system can be the RWD car when that is optimal and the FWD car when it is not.

The binding constraint is observability. Available signals are wheel speeds (a lagging indicator — slip must already have occurred), motor currents, IMU yaw and acceleration, steering angle, and a recursively estimated \(\hat\mu\). There is no forward-looking friction sensor: the vehicle cannot see the ice patch. So \(\varphi\) is held at a prior until slip is detected, then corrected. The default-\(\varphi\) analysis therefore remains the correct analysis for the first 50–100 ms, which is frequently the window in which the outcome is settled.

The consequence is that the operative advantage of a symmetric dual-PMSM system is not its default \(\varphi = 0.5\) but that \(\tau\) is small and symmetric in direction.

Architecture Shed front torque Add front torque Notes
Dual PMSM, both live ~10–20 ms ~10–20 ms inverter-limited, direction-symmetric
Rear PMSM + de-excited front ASM immediate (already ~0) \(O(\tau_r)\), tens of ms asymmetric; slow to add front
Front-primary + rear clutch immediate n/a slow to shed: clutch engagement plus speed synchronisation
Brake-based left–right (all of the above) 100–200 ms 100–200 ms ESC pump pressure build, plus detection latency

Under a prior of “I do not know which tyre will slip, or in which direction I will need to move torque,” a maximum-entropy default plus minimum direction-symmetric latency is the coherent design.

13 RWD in an EV: what actually survives

The historic case against RWD in winter was largely about ICE packaging: engine mass over the undriven axle, a light rear end, and driven-wheel unloading that worsened as the fuel tank emptied. A central floor battery plus a rear motor dissolves all of it — typical rear-motor EV distributions are near 48:52, better than most FWD ICE cars achieved. What remains:

  1. Mechanisms (B) and (C). Structural; independent of packaging.
  2. Regeneration lands on the rear axle by default. This one is new. Trailing-throttle oversteer existed in RWD ICE cars, but engine braking is modest; regeneration at \(\eta = 0.2\)–\(0.3\) is a far larger rearward longitudinal demand arriving the instant the driver lifts. It is the sharpest argument against RWD EVs specifically, and it barely existed before.

Towing, hill starts and loaded operation all favour RWD, since load goes over the driven axle.

14 Historical note: why Subaru committed pre-EV

The dominant reason is economic rather than dynamic. Subaru used a longitudinally mounted flat-four with an in-line gearbox, so the output shaft already pointed rearward and adding a rear propshaft was close to trivial. Competitors with transverse engines needed a transfer case, a 90° bevel drive and a propshaft — heavier, costlier, lossier. AWD cost Subaru perhaps 60–80 kg and modest efficiency against a much larger penalty for everyone else. It became a brand-wide standard rather than a trim option because it was cheap for Subaru specifically.

Genuine engineering benefits stacked on top, and they were not confined to longitudinal traction:

  • “Symmetrical” meant more than torque split. Equal-length halfshafts eliminate torque steer; the flat engine sits low, cutting \(h\) and therefore \(\Delta F_z = m a_y h / t\); left–right mass distribution is near-symmetric. Those are lateral benefits.
  • Passive mechanical transfer has zero detection latency. A viscous LSD or bevel centre differential responds to shaft speed difference physically — no sensor, no observer, no controller. The electric system must detect before it can act. Small but real regression.
  • Design target. Rally, Hokkaido, the US Snowbelt, New England, Colorado — distributions in which longitudinal traction genuinely is the binding constraint, and in which mechanisms (B) and (C) carry less weight.
  • Brand differentiation for a manufacturer at roughly a million units per year.

The costs were real: wide engines limiting steering lock, two cylinder heads, the EJ-series head gasket reputation, worse fuel economy.

The pointed observation for the EV era is that the structural reason is gone. Motor placement is free for every manufacturer; nobody needs a propshaft or a transfer case. Subaru’s cost advantage in building AWD has evaporated, and what remains is a brand commitment competing on level terms. This is why the case now has to rest on calibration and on \(\tau\) rather than on architecture.

15 Where the model is wrong

  1. Isotropic circle. Real tyres give an ellipse, \(\left(F_x / F_x^{\max}\right)^2 + \left(F_y / F_y^{\max}\right)^2 \le 1\), with \(F_x^{\max} \ne F_y^{\max}\). The circle is the special case.
  2. Constant \(\mu\). In reality \(\mu\) is a function of slip ratio \(\kappa\) and slip angle \(\alpha\), peaking at finite slip and falling thereafter (Pacejka’s Magic Formula). Post-peak the system is open-loop unstable, which is the entire reason ABS and traction control exist.
  3. Even split within the axle. An open differential equalises torque, not force. Under slip the axle’s total tractive force is \(2 \min_i F_{x,i}\), not \(2 \bar{F}_x\) — so the FWD case is somewhat worse than \(\lambda\) implies.
  4. Quasi-static load transfer. No roll dynamics, no damper transients, no suspension jacking, no aerodynamic effects.
  5. \(\varphi\) exogenous within an event. Justified only over the detection window (Section 9). Beyond it, \(\varphi\) is a closed-loop control output and the static comparison understates any architecture with small \(\tau\).
  6. \(w_A\) and \(w_B\) are stipulated. The three-mechanism decomposition establishes the sign structure of the trade-off, which is robust. The location of \(\varphi^*\) is not calibrated against crash or test data and should not be read as a quantitative recommendation.
  7. Mechanisms (B) and (C) are collapsed into one term. They scale similarly with rear tractive share but are physically distinct — one is a tyre-saturation consequence, the other a moment-balance effect present even below the limit.
  8. No ESC in the model. Stability control intervenes well before these open-loop limits and compresses the differences between architectures substantially.

16 References

Pacejka, H. B. (2012). Tire and Vehicle Dynamics (3rd ed.). Butterworth-Heinemann.

Milliken, W. F., & Milliken, D. L. (1995). Race Car Vehicle Dynamics. SAE International.

## R version 4.5.2 (2025-10-31)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.5 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8   LC_NUMERIC=C           LC_TIME=C.UTF-8       
##  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
##  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
## [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
## 
## time zone: UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] ggrepel_0.9.8    lubridate_1.9.5  forcats_1.0.1    stringr_1.6.0   
##  [5] dplyr_1.2.1      purrr_1.2.2      readr_2.2.0      tidyr_1.3.2     
##  [9] tibble_3.3.1     ggplot2_4.0.3    tidyverse_2.0.0  scales_1.4.0    
## [13] kableExtra_1.4.1 knitr_1.52      
## 
## loaded via a namespace (and not attached):
##  [1] sass_0.4.10        generics_0.1.4     xml2_1.6.0         stringi_1.8.9     
##  [5] hms_1.1.4          digest_0.6.39      magrittr_2.0.5     evaluate_1.0.5    
##  [9] grid_4.5.2         timechange_0.4.0   RColorBrewer_1.1-3 fastmap_1.2.0     
## [13] jsonlite_2.0.0     viridisLite_0.4.3  isoband_0.3.0      textshaping_1.0.5 
## [17] jquerylib_0.1.4    cli_3.6.6          rlang_1.3.0        withr_3.0.3       
## [21] cachem_1.1.0       yaml_2.3.12        otel_0.2.0         tools_4.5.2       
## [25] tzdb_0.5.0         vctrs_0.7.3        R6_2.6.1           lifecycle_1.0.5   
## [29] pkgconfig_2.0.3    pillar_1.11.1      bslib_0.12.0       gtable_0.3.6      
## [33] glue_1.8.1         Rcpp_1.1.2         systemfonts_1.3.2  xfun_0.60         
## [37] tidyselect_1.2.1   rstudioapi_0.19.0  farver_2.1.2       htmltools_0.5.9   
## [41] rmarkdown_2.32     svglite_2.2.2      labeling_0.4.3     compiler_4.5.2    
## [45] S7_0.2.2