The World-Model Evaluation Ladder › Transfer

Michael BrenndoerferJuly 31, 202618 min read
World Models HandbookAll chapters
Part 11 of 12Evaluation and Understanding
Chapter 61The World-Model Evaluation Ladder › Transfer
Reading section 6 of 9 · Transfer · 18 minSection 6 of 9 · 18 min

Book contents

Section 6 of 9 · 18 min read

The World-Model Evaluation Ladder › TransferOne section · Sections in this chapter6 / 9 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Transfer repeats an evaluation under a specified change. Several changes should be distinguished.

  • A dynamics shift replaces TT by a different transition law T′T' in the queried region. A predictor accurate for TT can become inaccurate there.
  • An initial-state shift changes occupancy and can expose poorly covered regions without changing the transition law.
  • A changed objective or action constraint changes which decisions are useful even when the dynamics predictor remains correct.
  • An observation shift can change the state or belief supplied to the controller while the physical transition law stays fixed.

Fitting data from TT alone establishes neither exact correctness under TT nor inevitable failure under T′T'. State what changed, what remains fixed, and whether retraining, adaptation, or extra observations are allowed. The experiment below changes only one true dynamics coefficient and keeps the candidate models, controller, target, initial state, and action grid fixed.

A scientific prior can help if it encodes structure that persists under the chosen shift; it can hurt if that structure changes. Better in-distribution fit does not universally imply worse transfer, and structural bias does not universally imply better transfer. [Part XII, Ch 2: Robustness, Calibration, and Safe Control] is planned to examine these questions more systematically.

Observation quality remains a separate part of the system, as discussed in [Part III, Ch 1: Observations, Sensors, and Multimodal Data]. Controlled changes to the perception pipeline can help distinguish observation failures from transition failures. The next chapters are planned to cover state and physics diagnostics.

A worked example: the model that wins one-step loses closed-loop

The example below is deliberately misspecified and small, so that everything is on the table. It is a compact illustration, not evidence that any particular architecture is inferior.

We use a fully observable linear system with a deterministic state-coupling structure plus additive Gaussian process noise:

st+1=a⋆st+b⋆ut+εts_{t+1} = a^\star s_t + b^\star u_t + \varepsilon_t

with a⋆=0.95a^\star = 0.95, b⋆=0.50b^\star = 0.50, and εt∼N(0,0.022)\varepsilon_t \sim \mathcal{N}(0, 0.02^2) i.i.d. across tt.

Where:

  • sts_t: the scalar state at time tt

  • utu_t: the scalar control action at time tt

  • a⋆a^\star: the true state-coupling coefficient

  • b⋆b^\star: the true action gain

  • εt\varepsilon_t: i.i.d. Gaussian process noise with standard deviation 0.020.02

  • State: scalar st∈Rs_t \in \mathbb{R} (fully observed).

  • Action: scalar ut∈Ru_t \in \mathbb{R}, with a hard bound ∣ut∣≤0.5|u_t| \le 0.5 for the planning tests.

  • Planning objective: squared predicted next-state distance to s⋆=1s^\star=1, with no action penalty.

  • Control score: mean absolute target error over true states 31–40. This diagnostic is not the cumulative squared-cost gap GG defined above.

  • Episode horizon: 40 steps for rollouts, 40 steps for closed loop.

  • Data policy: for one-step evaluation, actions are drawn i.i.d. from N(0,1)\mathcal{N}(0, 1). For rollout evaluation, we use the fixed sequence we specify in each rung.

  • Evaluation data: 800 independent synthetic transitions. The candidates are hand specified before sampling; no training set or fitted-model claim is involved.

  • Noise regime: Gaussian process noise is present in the one-step sample only. Rollout, intervention, and control tests use the noiseless transition, so their errors measure misspecification rather than different noise draws.

  • Model state: the Delayed candidate also carries the previous action. Its recurrent state is (s^t,ut−1)(\hat{s}_t,u_{t-1}), initialized with u−1=0u_{-1}=0 in rollout and control tests.

We compare three candidate models of the form

fm(xt,ut,pt)=amxt+bmut+cmptf_{\text{m}}(x_t,u_t,p_t)=a_{\text{m}}x_t+b_{\text{m}}u_t+c_{\text{m}}p_t

where:

  • fmf_{\text m}: the model's next-state prediction;
  • xtx_t: the input state, true sts_t for teacher forcing and replanning, predicted s^t\hat s_t for an open-loop rollout;
  • ptp_t: the previous applied action, ut−1u_{t-1}, with u−1=0u_{-1}=0 for the trajectory experiments;
  • ama_{\text{m}}: the model's state-coupling coefficient
  • bmb_{\text{m}}: the model's instantaneous action gain
  • cmc_{\text{m}}: the model's action-lag coefficient, capturing the influence of the previous action

The following candidates are chosen to expose different failure modes.

  • Aggressive (am=1.02a_{\text{m}} = 1.02): captures the state coupling closely but overshoots it into instability.
  • Conservative (am=0.80a_{\text{m}} = 0.80): underestimates the state coupling.
  • Delayed (am=0.95a_{\text{m}} = 0.95, cm=0.20c_{\text{m}} = 0.20): gets the state coupling right but adds a spurious action lag.

These are hand-constructed misspecifications, not fitted solutions. Our goal is not to argue that any particular model class fails; it is to show that each rung rewards different inductive biases and that a lower rung does not by itself guarantee a higher one without additional linking assumptions. Notice that each model is a plausible way to be wrong about the same system. The Aggressive model is approximately correct as a one-step predictor because its coefficient error is small; the Conservative model trades a larger bias for more conservative behavior; the Delayed model gets the coupling right but invents a spurious dependence on the previous action. These hand-selected deviations let us isolate what happens when different coefficient errors are pushed through the evaluation tests.

In[8]:
Code
import numpy as np

# Ground-truth one-dimensional controlled dynamics:
#   s_{t+1} = A_TRUE * s_t + B_TRUE * u_t + noise
A_TRUE = 0.95
B_TRUE = 0.50
NOISE_STD = 0.02

# Candidate models of the form  s_hat_{t+1} = a * s_t + b * u_t + c * u_{t-1}.
MISS = {
    "Aggressive (a=1.02)": {"a": 1.02, "b": 0.50, "c": 0.00},
    "Conservative (a=0.80)": {"a": 0.80, "b": 0.50, "c": 0.00},
    "Delayed (a=0.95, c=0.20)": {"a": 0.95, "b": 0.50, "c": 0.20},
}


def model_step(model, s, u, u_prev):
    return model["a"] * s + model["b"] * u + model["c"] * u_prev

Now the rung-one evaluation, on 800 i.i.d. transitions.

In[9]:
Code
rng = np.random.default_rng(20240501)
N_EVAL = 800

s_t = rng.normal(0.0, 1.0, size=N_EVAL)
u_prev_t = rng.normal(0.0, 1.0, size=N_EVAL)
u_t = rng.normal(0.0, 1.0, size=N_EVAL)
s_next_t = A_TRUE * s_t + B_TRUE * u_t + rng.normal(0.0, NOISE_STD, size=N_EVAL)

one_step_mse = {}
one_step_losses = {}
expected_one_step_mse = {}
gaussian_mse_se = {}
for name, model in MISS.items():
    pred = model["a"] * s_t + model["b"] * u_t + model["c"] * u_prev_t
    losses = (pred - s_next_t) ** 2
    one_step_losses[name] = losses
    one_step_mse[name] = float(np.mean(losses))
    expected_risk = (
        (model["a"] - A_TRUE) ** 2
        + (model["b"] - B_TRUE) ** 2
        + model["c"] ** 2
        + NOISE_STD**2
    )
    expected_one_step_mse[name] = expected_risk
    gaussian_mse_se[name] = expected_risk * np.sqrt(2 / N_EVAL)

paired_mse_se = {}
names = list(MISS)
for left in range(len(names)):
    for right in range(left + 1, len(names)):
        pair = (names[left], names[right])
        differences = one_step_losses[pair[0]] - one_step_losses[pair[1]]
        paired_mse_se[pair] = float(
            np.std(differences, ddof=1) / np.sqrt(N_EVAL)
        )
Out[10]:
Console
         Aggressive (a=1.02)  sample MSE = 0.005843; population MSE = 0.005300; Gaussian marginal SE = 0.000265
       Conservative (a=0.80)  sample MSE = 0.024686; population MSE = 0.022900; Gaussian marginal SE = 0.001145
    Delayed (a=0.95, c=0.20)  sample MSE = 0.040841; population MSE = 0.040400; Gaussian marginal SE = 0.002020
Paired Aggressive (a=1.02) minus Conservative (a=0.80): sample gap -0.018842; estimated SE 0.000938
Paired Aggressive (a=1.02) minus Delayed (a=0.95, c=0.20): sample gap -0.034998; estimated SE 0.002047
Paired Conservative (a=0.80) minus Delayed (a=0.95, c=0.20): sample gap -0.016156; estimated SE 0.002321

On these independent standard-normal inputs, each model's residual is a zero-mean Gaussian linear combination of state, action, previous action, and independent process noise. Its population MSE is therefore

(am−0.95)2+(bm−0.50)2+cm2+0.022.(a_{\mathrm m}-0.95)^2+(b_{\mathrm m}-0.50)^2 +c_{\mathrm m}^2+0.02^2.

The resulting risks are 0.00530.0053 for Aggressive, 0.02290.0229 for Conservative, and 0.04040.0404 for Delayed. Aggressive's state coefficient error is smaller than Conservative's; Delayed matches that coefficient exactly. Aggressive has lower total risk than Delayed because Delayed's spurious previous-action term adds variance 0.040.04. The sampled MSEs differ from these population risks, but retain the same order for the stated seed. No candidate was fitted to the sample.

For independent zero-mean Gaussian residuals of variance vv, the variance of their squared value is 2v22v^2. Thus the sample MSE has standard error v2/Mv\sqrt{2/M}: at M=800M=800, five percent of the population MSE. The code prints that marginal calculation and separately estimates the standard error of each paired loss difference. The latter includes the covariance between model losses scored on the same transitions.

Now for rung two. We fix s0=0.3s_0 = 0.3 and apply constant actions ut=0.2u_t = 0.2 over 40 steps, then measure trajectory RMSE.

In[11]:
Code
HORIZON = 40
S_INIT = 0.3
actions_const = np.full(HORIZON, 0.20)

true_traj_const = np.zeros(HORIZON + 1)
true_traj_const[0] = S_INIT
for t in range(HORIZON):
    true_traj_const[t + 1] = (
        A_TRUE * true_traj_const[t] + B_TRUE * actions_const[t]
    )

rollout_rmse_const = {}
for name, model in MISS.items():
    traj = np.zeros(HORIZON + 1)
    traj[0] = S_INIT
    u_prev = 0.0
    for t in range(HORIZON):
        traj[t + 1] = model_step(model, traj[t], actions_const[t], u_prev)
        u_prev = actions_const[t]
    rollout_rmse_const[name] = float(
        np.sqrt(np.mean((traj[1:] - true_traj_const[1:]) ** 2))
    )
Out[12]:
Console
    Delayed (a=0.95, c=0.20)  RMSE = 0.4936
       Conservative (a=0.80)  RMSE = 0.8969
         Aggressive (a=1.02)  RMSE = 2.3856

Under a constant positive push u=0.2u = 0.2, the true system's noiseless steady state is b⋆u/(1−a⋆)=(0.50)(0.2)/(1−0.95)=0.1/0.05=2.0b^\star u / (1 - a^\star) = (0.50)(0.2) / (1 - 0.95) = 0.1 / 0.05 = 2.0. The Aggressive model has 1.02>11.02 > 1, so its rollout diverges. The Conservative model has steady state 0.1/(1−0.80)=0.1/0.20=0.50.1 / (1 - 0.80) = 0.1 / 0.20 = 0.5, well below the truth. The Delayed model acquires an effective action gain bm+cm=0.50+0.20=0.70b_{\text{m}} + c_{\text{m}} = 0.50 + 0.20 = 0.70, giving steady state (0.70)(0.2)/(1−0.95)=0.14/0.05=2.8(0.70)(0.2) / (1 - 0.95) = 0.14 / 0.05 = 2.8, incidentally closer to the true value than the Conservative model's. Delayed wins rung two, despite losing rung one. The gain 0.700.70 is not the true gain 0.500.50. Delayed wins this finite-horizon comparison because its state decay coefficient matches the truth and its accumulated gain error is smaller than the competing models' trajectory errors for this initial state and input. This is not a claim that the lagged model has correct constant-input dynamics.

For a controlled-intervention trajectory diagnostic, change the constant input to ut=0.4(−1)tu_t=0.4(-1)^t over the same 40 steps. This is another open-loop trajectory test under a deliberately changed action sequence. It is not the intervention-response difference ΔH\Delta_H defined earlier, and it does not establish causal identification. The simulator supplies the true dynamics by construction.

In[13]:
Code
def paired_rollouts(model, actions, initial=0.3):
    truth = np.zeros(len(actions) + 1)
    predicted = np.zeros_like(truth)
    truth[0] = predicted[0] = initial
    previous = 0.0
    for step, action in enumerate(actions):
        truth[step + 1] = A_TRUE * truth[step] + B_TRUE * action
        predicted[step + 1] = model_step(
            model, predicted[step], action, previous
        )
        previous = action
    return truth, predicted


actions_alt = 0.4 * (-1.0) ** np.arange(HORIZON)
rollout_rmse_alt = {}
long_alt_rmse = {}
alternating_amplitude = {"Truth": 0.4 * B_TRUE / (1 + A_TRUE)}
for name, model in MISS.items():
    truth, predicted = paired_rollouts(model, actions_alt)
    rollout_rmse_alt[name] = float(
        np.sqrt(np.mean((predicted[1:] - truth[1:]) ** 2))
    )
    truth_long, predicted_long = paired_rollouts(
        model, 0.4 * (-1.0) ** np.arange(500)
    )
    long_alt_rmse[name] = float(
        np.sqrt(np.mean((predicted_long[1:] - truth_long[1:]) ** 2))
    )
    if abs(model["a"]) < 1:
        alternating_amplitude[name] = (
            0.4 * abs(model["b"] - model["c"]) / (1 + model["a"])
        )
Out[14]:
Console
Aggressive (a=1.02): alternating RMSE H=40 0.504376; H=500 1807.149336
Conservative (a=0.80): alternating RMSE H=40 0.135162; H=500 0.039716
Delayed (a=0.95, c=0.20): alternating RMSE H=40 0.045496; H=500 0.041403
Truth: steady alternating amplitude 0.1025641
Conservative (a=0.80): steady alternating amplitude 0.1111111
Delayed (a=0.95, c=0.20): steady alternating amplitude 0.0615385

Delayed wins the 40-step alternating-trajectory test, but not because its steady alternating response is most accurate. After the initial step, ut−1=−utu_{t-1}=-u_t, so its effective alternating gain is 0.50−0.20=0.300.50-0.20=0.30. For ∣am∣<1|a_{\mathrm m}|<1, substituting st=−K(−1)ts_t=-K(-1)^t into the forced recurrence gives the signed coefficient K=0.4(bm−cm)/(1+am)K=0.4(b_{\mathrm m}-c_{\mathrm m})/(1+a_{\mathrm m}). The amplitude magnitude is ∣K∣|K|. All displayed stable candidates have bm>cmb_{\mathrm m}>c_{\mathrm m}, so their coefficients and magnitudes agree. The true amplitude is 0.10256410.1025641, Conservative's is 0.11111110.1111111, and Delayed's is 0.06153850.0615385. Delayed instead benefits at this short horizon from matching the true transient decay coefficient. At 500 steps Conservative has lower trajectory RMSE than Delayed, as the printed results show. Aggressive is unstable, so its formal forced periodic solution is not an attracting steady response.

We also compute the actual response-difference test. Use the constant input as baseline and the alternating input as intervention, starting both trajectories at s0=0.3s_0=0.3 with previous action zero. Compare their final-state differences, not merely the intervened trajectory:

In[15]:
Code
intervention_error = {}
intervention_responses = {}
for name, model in MISS.items():
    truth_base, predicted_base = paired_rollouts(model, actions_const)
    truth_changed, predicted_changed = paired_rollouts(model, actions_alt)
    delta_true = truth_changed[-1] - truth_base[-1]
    delta_model = predicted_changed[-1] - predicted_base[-1]
    intervention_responses[name] = (float(delta_true), float(delta_model))
    intervention_error[name] = float(abs(delta_model - delta_true))
Out[16]:
Console
Delayed (a=0.95, c=0.20): true response -1.832359; model response -2.477563; final response error 0.645204
Conservative (a=0.80): true response -1.832359; model response -0.611030; final response error 1.221329
Aggressive (a=1.02): true response -1.832359; model response -5.920590; final response error 4.088231

This response metric and the alternating-trajectory RMSE answer different questions, even if they select the same winner here. Equal additive trajectory biases cancel in the difference, but general dynamical errors need not. The metric tests these two known simulator interventions only; it does not certify unseen interventions or an observationally learned causal model.

Rung four is closed-loop planning. We use a one-step MPC that searches 201 equally spaced actions in [−0.5,0.5][-0.5,0.5] and minimizes squared predicted next-state distance to s⋆=1s^\star=1. It then executes the chosen action in the true deterministic environment and reobserves the state. This costs 201 candidate one-step predictions per control step, with no action penalty and no lookahead beyond one step. The grid enforces the action constraint by construction; no state-safety constraint is imposed.

In[17]:
Code
U_GRID = np.linspace(-0.5, 0.5, 201)


def mpc_choose(model, s, u_prev, target):
    preds = model["a"] * s + model["b"] * U_GRID + model["c"] * u_prev
    idx = int(np.argmin((preds - target) ** 2))
    return float(U_GRID[idx])


def closed_loop(model, s_start, target, steps, a_env, b_env):
    s = s_start
    u_prev = 0.0
    traj = [s]
    for _ in range(steps):
        u = mpc_choose(model, s, u_prev, target)
        s = a_env * s + b_env * u
        traj.append(s)
        u_prev = u
    return np.array(traj)


STEPS = 40
TARGET = 1.0

closed_loop_error = {}
for name, model in MISS.items():
    traj = closed_loop(
        model,
        s_start=0.0,
        target=TARGET,
        steps=STEPS,
        a_env=A_TRUE,
        b_env=B_TRUE,
    )
    tail = traj[-10:]
    closed_loop_error[name] = float(np.mean(np.abs(tail - TARGET)))

transfer_error = {}
A_SHIFT = 0.80
for name, model in MISS.items():
    traj = closed_loop(
        model,
        s_start=0.0,
        target=TARGET,
        steps=STEPS,
        a_env=A_SHIFT,
        b_env=B_TRUE,
    )
    tail = traj[-10:]
    transfer_error[name] = float(np.mean(np.abs(tail - TARGET)))
Out[18]:
Console
Closed-loop tracking error (same environment):
      Delayed (a=0.95, c=0.20)  |s - s*| = 0.0200
           Aggressive (a=1.02)  |s - s*| = 0.0654
         Conservative (a=0.80)  |s - s*| = 0.1765

Transfer to a changed environment (a shifts from 0.95 to 0.80):
         Conservative (a=0.80)  |s - s*| = 0.0000
           Aggressive (a=1.02)  |s - s*| = 0.1803
      Delayed (a=0.95, c=0.20)  |s - s*| = 0.1875

An idealized continuous-action controller helps explain the tracking bias. Assume deterministic dynamics, an inactive action bound, no action penalty, and one-step squared next-state tracking toward target gg. With bm≠0b_{\text{m}}\ne0, its unconstrained choice is

ut=g−amst−cmut−1bmu_t=\frac{g-a_{\text{m}}s_t-c_{\text{m}}u_{t-1}}{b_{\text{m}}}

At a fixed point both state and action are constant. The true environment therefore satisfies (1−aenv)s∞=benvu∞(1-a_{\text{env}})s_\infty=b_{\text{env}}u_\infty, while the model's target equation becomes g=ams∞+(bm+cm)u∞g=a_{\text{m}}s_\infty+(b_{\text{m}}+c_{\text{m}})u_\infty. Eliminating u∞u_\infty yields

s∞=benvg(bm+cm)(1−aenv)+benvams_\infty= \frac{b_{\text{env}}g} {(b_{\text{m}}+c_{\text{m}})(1-a_{\text{env}}) +b_{\text{env}}a_{\text{m}}}

where s∞s_\infty is the continuous controller's equilibrium, aenva_{\text{env}} and benvb_{\text{env}} are true environment coefficients, and am,bm,cma_{\text{m}},b_{\text{m}},c_{\text{m}} are model coefficients. The denominator must be nonzero. This algebra finds an equilibrium, not a guarantee of stability or feasibility; those must be checked separately.

In[19]:
Code
def continuous_equilibrium(model, a_env, b_env, target):
    denominator = (model["b"] + model["c"]) * (1 - a_env) + b_env * model["a"]
    return b_env * target / denominator


continuous_fixed_points = {
    "same": {
        name: continuous_equilibrium(model, A_TRUE, B_TRUE, TARGET)
        for name, model in MISS.items()
    },
    "transfer": {
        name: continuous_equilibrium(model, A_SHIFT, B_TRUE, TARGET)
        for name, model in MISS.items()
    },
}
Out[20]:
Console
same continuous-action equilibria:
  Aggressive (a=1.02): 0.9345794393
  Conservative (a=0.80): 1.1764705882
  Delayed (a=0.95, c=0.20): 0.9803921569
transfer continuous-action equilibria:
  Aggressive (a=1.02): 0.8196721311
  Conservative (a=0.80): 1.0000000000
  Delayed (a=0.95, c=0.20): 0.8130081301

The continuous same-environment equilibria are approximately 0.93457940.9345794 (Aggressive), 1.17647061.1764706 (Conservative), and 0.98039220.9803922 (Delayed). In transfer they are 0.81967210.8196721, 11, and 0.81300810.8130081. The lagged-action term remains in the transfer equation; dropping it would give the wrong Delayed equilibrium.

The implemented controller searches 201 discrete actions instead. It need not converge to those continuous equilibria: grid quantization can produce small periodic trajectories. We report the actual mean absolute tracking error over states 31 through 40, not an analytically inferred bias. Delayed has the lowest same-environment tail error, while Conservative has the lowest transfer tail error. Conservative is worst in the same-environment closed-loop comparison, not least accurate on every metric.

In[21]:
Code
delayed_name = "Delayed (a=0.95, c=0.20)"
delayed_same_long = closed_loop(
    MISS[delayed_name], 0.0, TARGET, 1000, A_TRUE, B_TRUE
)
delayed_transfer_long = closed_loop(
    MISS[delayed_name], 0.0, TARGET, 1000, A_SHIFT, B_TRUE
)
delayed_same_40 = closed_loop(
    MISS[delayed_name], 0.0, TARGET, STEPS, A_TRUE, B_TRUE
)
delayed_transfer_tail_mae = float(
    np.mean(np.abs(delayed_transfer_long[-10:] - TARGET))
)
Out[22]:
Console
Delayed actual state after 40 same-environment steps: 0.9794848891
Delayed last five same-environment states: [0.98050003 0.97897503 0.98002627 0.98102496 0.97947371]
Delayed last two transfer states: [0.81527778 0.80972222]
Delayed long-run transfer tail MAE: 0.1875000

From this initialization, Delayed approaches a five-step cycle in the same environment and a two-step cycle in transfer. The transfer cycle has states approximately 0.81527780.8152778 and 0.80972220.8097222, with corresponding executed actions 0.3350.335 and 0.3150.315; its tail MAE is 0.18750.1875. These grid trajectories explain why a finite-run statistic differs from a continuous-action fixed point. Neither statistic is an architectural ranking.

Closing the loop also introduces a decision baseline. An oracle one-step controller using the true coefficients separates dynamics misspecification from action-grid and startup effects in this toy; a zero-action baseline shows the value of control. This is a diagnostic baseline, not a claim that oracle dynamics are available in deployments.

In[23]:
Code
oracle_model = {"a": A_TRUE, "b": B_TRUE, "c": 0.0}
oracle_traj = closed_loop(oracle_model, 0.0, TARGET, STEPS, A_TRUE, B_TRUE)
oracle_tail_mae = float(np.mean(np.abs(oracle_traj[-10:] - TARGET)))
zero_action_tail_mae = float(np.mean(np.abs(np.zeros(10) - TARGET)))
Out[24]:
Console
True-dynamics one-step controller tail MAE: 0.0002316
Zero-action baseline tail MAE: 1.0000000

The actual grid-controller trajectories show the residual tracking errors.

In[25]:
Code
closed_loop_trajs = {}
for name, model in MISS.items():
    closed_loop_trajs[name] = closed_loop(
        model,
        s_start=0.0,
        target=TARGET,
        steps=STEPS,
        a_env=A_TRUE,
        b_env=B_TRUE,
    )
Out[26]:
Visualization
Grid-controller trajectories showing undertracking, overtracking, and small periodic error.
Actual grid-controller trajectories under the same environment. Aggressive tracks below the target, Conservative above it, and Delayed has the smallest tail tracking error. Delayed also won the finite constant-input rollout test; this figure does not demonstrate an open-loop versus closed-loop winner reversal.

Now compare rankings without dividing by a near-zero winner error. The transfer winner's error is almost zero, so dividing other models' errors by it would create enormous ratios and hide differences on the other tests. We show within-test ranks, with 1 best. Ranks discard the size of the gaps; the printed raw metrics retain that information. Do not sum the ranks into a universal score.

Out[28]:
Visualization
Five tests with three model ranks; winners are Aggressive, Delayed, Delayed, Delayed, Conservative.
Within-test ranks, with 1 best and 3 worst. Aggressive leads on one-step MSE, Delayed on the finite constant-input rollout, response-difference test, and same-environment closed loop, and Conservative on transfer. Rank spacing does not encode error magnitude; refer to the computed raw metrics for those gaps.

The pattern is exactly what the ladder is designed to expose: different rungs can favour different winners.

  • One-step MSE favors Aggressive on the stated independent Gaussian inputs.
  • Constant-input rollout favors Delayed at the specified 40-step horizon, despite its incorrect gain.
  • The response-difference metric favors Delayed for this baseline/intervention pair. The additional alternating-trajectory test is not the same metric.
  • Same-environment grid control also favors Delayed; the example does not show a winner reversal between that rollout and that controller.
  • Transfer favors Conservative because its coefficients match the changed environment more closely. This is no general guarantee of robust transfer.

These rankings are conditional on the test distribution, action sequence, horizon, initial state, and scoring rule. A scalar chosen for a declared deployment objective can be useful. Report that choice explicitly, and retain the other diagnostics when they describe additional intended uses.