Generated by Claude Opus 5
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.
| 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 |
\(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 |
| 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 |
| 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]\) |
| 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 |
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}\]
\[\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.
\[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.
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\]
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)\]
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.
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")
)
}
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.
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.
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.
Sections 3–4 evaluate the objective at the front axle only. That is blind to the asymmetry in consequences:
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.
\[\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.
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.
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."
)
)
| allocation | front_demand | rear_demand | summed_loss |
|---|---|---|---|
| Concentrated (FWD) | 0.8 | 0.0 | 0.400 |
| Symmetric (50:50) | 0.4 | 0.4 | 0.167 |
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.
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.")
| 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."
)
| 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.
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.
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:
Towing, hill starts and loaded operation all favour RWD, since load goes over the driven axle.
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:
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.
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