Frontier Questions and Research Roadmaps › Experiment 1: Prediction Fidelity Versus Decision Usefulness
World Models HandbookAll chapters Frontier Questions and Research Roadmaps › Experiment 1: Prediction Fidelity Versus Decision UsefulnessOne section · Sections in this chapter5 / 11 ↓
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
An aggregate observation metric can rank two models in the opposite order from their usefulness for a particular control task. We construct an example, not a general ranking of world-model architectures. All learned models receive the same transitions and actions. The state-focused model additionally receives the declared task-relevant coordinate set, so this is an information-abstraction comparison rather than a parameter-matched architecture benchmark.
The observation contains a small controlled state and a large, persistent background. A model can devote its limited latent capacity to reconstructing that background, reduce observation error, and still omit the coordinates the controller needs. We measure the variance hierarchy, per-coordinate prediction errors, and real-simulator control costs rather than assuming that the intended mechanism occurred.
A Stable Controlled Process and an Autonomous Background
The decision-relevant state is . Both coordinates and the scalar action use dimensionless units. The environment evolves as
Here is the positioning coordinate, is a motion coordinate, and is a normalized command. This is a discrete synthetic law, not an Euler approximation to a specified physical robot. The decay terms prevent the unbounded random walk that a long randomly accelerated free particle would exhibit. Starting from and , the rectangle , is invariant: the first update has magnitude at most , and the monotone velocity map on has magnitude at most . The small drag term makes the learned linear predictors approximations rather than exact copies of the plant.
An autonomous nuisance state follows
The initial makes this process stationary in distribution, with unit marginal variance. Its eight observed channels are , where has its first row equal to one in the first four columns and zero elsewhere, and its second row equal to one in the final four columns. Each nuisance channel therefore has marginal standard deviation 20, but the eight channels have rank two and are not independent observations. This deliberately redundant background strongly influences unnormalized observation loss. Its transition law contains neither the controlled state, action nor goal. That autonomy does not imply statistical independence from chosen actions: a learned feedback controller can use nuisance observations and thereby couple its commands, and subsequent controlled states, to the background.
The observation is . Its coordinates have different meanings and scales; averaging their squared errors embeds a measurement choice. The task is to approach an episode-specific target . Nuisance channels do not appear in the task cost.
import numpy as np
AR_N = 0.95
INNOV_N = np.sqrt(1.0 - AR_N**2)
NUISANCE_STD = 20.0
W_NUISANCE = np.zeros((2, 8))
W_NUISANCE[0, :4] = 1.0
W_NUISANCE[1, 4:] = 1.0
def true_step(x, v, action):
return 0.85 * x + 0.2 * v, 0.8 * v + 0.2 * action - 0.01 * v * np.abs(v)
def nuisance_obs(n):
return NUISANCE_STD * (n @ W_NUISANCE)
def nuisance_step(n, rng):
return AR_N * n + INNOV_N * rng.normal(size=2)The eight observation channels cannot reveal future nuisance innovations. Even the analytic conditional-mean reference must predict their mean, not substitute realized future values. That restriction will keep the reference causal.
Independent Trajectories and Declared Splits
We collect one hundred training trajectories, each with forty random-action transitions and an independent reset. The test set uses a different seed and a fixed task-like behavior policy. Every complete trajectory belongs to only one split. We concatenate transition pairs, not adjacent rows across resets, so the last state of one episode never becomes the first state of another.
def collect_transitions(n_episodes, n_steps, seed, task_like=False):
rng = np.random.default_rng(seed)
previous, following, actions = [], [], []
for _ in range(n_episodes):
x, v = rng.uniform(-1.5, 1.5), rng.uniform(-1.0, 1.0)
n = rng.normal(size=2)
target = rng.uniform(-0.8, 0.8)
for _ in range(n_steps):
obs = np.concatenate(([x, v], nuisance_obs(n)))
action = (
1.8 * (target - x) - 1.2 * v + 0.2 * rng.normal()
if task_like
else rng.uniform(-1.0, 1.0)
)
action = float(np.clip(action, -1.0, 1.0))
x, v = true_step(x, v, action)
n = nuisance_step(n, rng)
previous.append(obs)
following.append(np.concatenate(([x, v], nuisance_obs(n))))
actions.append(action)
return np.array(previous), np.array(following), np.array(actions)
train_prev, train_next, act_train = collect_transitions(100, 40, seed=1)
test_prev, test_next, act_test = collect_transitions(
100, 40, seed=2, task_like=True
)
train_std = train_prev.std(axis=0)
variance_ratio = float(np.mean(train_std[2:]) / np.mean(train_std[:2]))Train/test transition shapes: (4000, 10), (4000, 10) Relevant channel standard deviations: [0.33752297 0.23911176] Nuisance channel standard deviations: [19.3815531 19.3815531 19.3815531 19.3815531 19.47159052 19.47159052 19.47159052 19.47159052] Mean nuisance/relevant standard-deviation ratio: 67.4 Maximum training |x| and |v|: [1.48468296 0.99669796]
These diagnostics describe the actual finite sample. The analytic standard deviation of 20 is a population quantity, not a claim that a sampled trajectory has exactly that standard deviation. The stable controlled process keeps the relevant coordinates bounded without clipping them at a hidden threshold. Random actions provide variation over the allowed command interval; they do not establish coverage of every state-action pair.
Three Learned Models and a Conditional-Mean Reference
The observation models obtain a PCA basis from training inputs and fit a linear action-conditioned transition in that basis. We compare two and four components. The four-dimensional observation subspace contains both relevant coordinates and the two nuisance dimensions. The state-focused model fits only the first two coordinates and fills unmodeled background predictions with the training mean.
These models differ in what they represent and in parameter count. The state-focused model knows which coordinates the task cost uses. PCA knows neither the cost nor that coordinate assignment. The comparison tests the mismatch between an unweighted reconstruction objective and this declared task, not whether supervised coordinate selection is automatically available for a real robot.
mean_o = train_prev.mean(axis=0)
_, singular_values, Vt = np.linalg.svd(train_prev - mean_o, full_matrices=False)
def fit_latent_model(k):
P = Vt[:k].T
current = (train_prev - mean_o) @ P
following = (train_next - mean_o) @ P
features = np.column_stack([current, act_train, np.ones(len(act_train))])
coef, *_ = np.linalg.lstsq(features, following, rcond=None)
return {"P": P, "A": coef[:k].T, "B": coef[k], "c": coef[k + 1]}
def fit_state_model():
features = np.column_stack(
[train_prev[:, :2], act_train, np.ones(len(act_train))]
)
coef, *_ = np.linalg.lstsq(features, train_next[:, :2], rcond=None)
return {"A": coef[:2].T, "B": coef[2], "c": coef[3]}
m_obs2, m_obs4, m_state = (
fit_latent_model(2),
fit_latent_model(4),
fit_state_model(),
)
relevant_loading = float(np.linalg.norm(m_obs2["P"][:2]))Two-component basis loading on relevant coordinates: 0.00021 Two/four-component action coefficient norms: 0.10790, 0.22639 State-model action coefficient: [3.68779459e-17 2.00045596e-01]
A small relevant-coordinate loading supports the intended compression mechanism in this sample. It need not be exactly zero: finite-sample correlations can leak action-related information into the nuisance-dominated basis. A nonzero action coefficient alone does not establish useful action effects; we also measure predictions and control outcomes.
For a latent dimension , the fitted matrices have shapes , , . A batch of latent rows is advanced as , giving predictions. The state model has and receives state rows directly.
One-Step Error Without Future Leakage
All predictors receive the current observation and action. The analytic reference uses the true controlled law and the nuisance conditional mean . It does not observe the next nuisance innovation. Its aggregate error is therefore positive (the nuisance channels contribute the residual variance of the process) even though its prediction of the noiseless relevant transition is exact.
def latent_prediction(model, observations, actions):
latent = (observations - mean_o) @ model["P"]
following = (
latent @ model["A"].T + np.outer(actions, model["B"]) + model["c"]
)
return mean_o + following @ model["P"].T
def state_prediction(model, observations, actions):
following = (
observations[:, :2] @ model["A"].T
+ np.outer(actions, model["B"])
+ model["c"]
)
background = np.tile(mean_o[2:], (len(observations), 1))
return np.column_stack([following, background])
def oracle_prediction(observations, actions):
x, v = true_step(observations[:, 0], observations[:, 1], actions)
return np.column_stack([x, v, AR_N * observations[:, 2:]])
predictions = {
"M_obs (2 comps)": latent_prediction(m_obs2, test_prev, act_test),
"M_obs (4 comps)": latent_prediction(m_obs4, test_prev, act_test),
"M_state (relevant)": state_prediction(m_state, test_prev, act_test),
"Oracle": oracle_prediction(test_prev, act_test),
}
per_channel = {
name: np.mean((prediction - test_next) ** 2, axis=0)
for name, prediction in predictions.items()
}
agg_mse = {name: float(error.mean()) for name, error in per_channel.items()}
relevant_rmse = {
name: float(np.sqrt(error[:2].mean()))
for name, error in per_channel.items()
}
metric_names = list(predictions)
metric_values = np.array([agg_mse[name] for name in metric_names])
relevant_values = np.array([relevant_rmse[name] for name in metric_names])model aggregate MSE relevant RMSE M_obs (2 comps) 31.793 0.2638 M_obs (4 comps) 31.844 0.0004 M_state (relevant) 342.107 0.0004 Oracle 31.707 0.0000
The state-focused model's background mean is an explicit output-completion rule, not an optimal nuisance predictor. A learned nuisance predictor could change its aggregate error and ranking without changing its controller. The direction and size of that change would need measurement. The relevant-coordinate RMSE and controller cost therefore answer questions that the aggregate score alone cannot.


The observed comparison is an existence demonstration. In particular, the state-focused model's mean-completion rule produces a far larger aggregate observation error than the two-component observation model (because the mean-completion residuals on the large-scale nuisance channels dominate), even though the state-focused model has the smaller relevant-coordinate error. This shows that an aggregate score dominated by decision-irrelevant channels and a task-relevant score can rank the models oppositely. The four-component model is a capacity ablation, not a failure that must be manufactured. Retaining the controlled subspace can improve both prediction and control.
Closed-Loop Evaluation with Matched Candidate Actions
All model-based controllers use random-shooting MPC with sequences of actions. They minimize the same predicted cost,
Here are the model states after candidate action , and is the known episode goal. Each controller executes only the first action and replans from the next observation. The real simulator measures cost after that action, using the same coordinate weights. This keeps the prediction horizon's indexing consistent with evaluation.
A model-free PD controller provides a baseline. Its gains are tuned on separate simulator episodes, and the final comparison uses held-out episodes. The analytic MPC controller has the true dynamics, but finite-horizon random shooting is not an optimal-control solver; its cost is a reference, not a universal lower bound. Candidate action arrays and environmental resets use the same seeds across model-based controllers. The prediction model is the changed component; earlier selected actions can then produce different observations and subsequent decisions.
def make_linear_step(model, latent):
def step(states, actions):
following = (
states @ model["A"].T + np.outer(actions, model["B"]) + model["c"]
)
decoded = mean_o + following @ model["P"].T if latent else following
return following, decoded[:, 0], decoded[:, 1]
return step
def oracle_batch_step(states, actions):
x, v = true_step(states[:, 0], states[:, 1], actions)
return np.column_stack([x, v]), x, v
def plan(step_fn, initial, target, rng, K=200, H=8):
actions = rng.uniform(-1.0, 1.0, size=(K, H))
states = np.repeat(np.asarray(initial)[None, :], K, axis=0)
costs = np.zeros(K)
for h in range(H):
states, x, v = step_fn(states, actions[:, h])
costs += (x - target) ** 2 + 0.05 * v**2 + 0.01 * actions[:, h] ** 2
return float(actions[np.argmin(costs), 0])def model_controller_factory(step_fn, initial_fn):
def factory(seed):
rng = np.random.default_rng(seed)
return lambda obs, target: plan(step_fn, initial_fn(obs), target, rng)
return factory
def pd_factory(kp, kd):
return lambda seed: (
lambda obs, target: float(
np.clip(kp * (target - obs[0]) - kd * obs[1], -1.0, 1.0)
)
)
def evaluate(factory, n_episodes=20, n_steps=40, seed=7):
rng = np.random.default_rng(seed)
costs = []
for episode in range(n_episodes):
controller = factory(1000 + episode)
x, v = rng.uniform(-1.5, 1.5), rng.uniform(-1.0, 1.0)
target = rng.uniform(-0.8, 0.8)
n, total = rng.normal(size=2), 0.0
for _ in range(n_steps):
observation = np.concatenate(([x, v], nuisance_obs(n)))
action = float(np.clip(controller(observation, target), -1.0, 1.0))
x, v = true_step(x, v, action)
n = nuisance_step(n, rng)
total += (x - target) ** 2 + 0.05 * v**2 + 0.01 * action**2
costs.append(total)
return np.array(costs)
pd_grid = [
(kp, kd) for kp in (0.5, 1.0, 1.5, 2.0) for kd in (0.5, 1.0, 1.5, 2.0)
]
pd_scores = {
g: evaluate(pd_factory(*g), n_episodes=10, seed=11).mean() for g in pd_grid
}
best_pd = min(pd_scores, key=pd_scores.get)
controller_factories = {
"PD (no model)": pd_factory(*best_pd),
"MPC M_obs (2 comps)": model_controller_factory(
make_linear_step(m_obs2, True), lambda obs: (obs - mean_o) @ m_obs2["P"]
),
"MPC M_obs (4 comps)": model_controller_factory(
make_linear_step(m_obs4, True), lambda obs: (obs - mean_o) @ m_obs4["P"]
),
"MPC M_state (relevant)": model_controller_factory(
make_linear_step(m_state, False), lambda obs: obs[:2]
),
"MPC oracle": model_controller_factory(
oracle_batch_step, lambda obs: obs[:2]
),
}
cl_costs = {
name: evaluate(factory) for name, factory in controller_factories.items()
}
cl_mean = {name: float(costs.mean()) for name, costs in cl_costs.items()}
cl_sem = {
name: float(costs.std(ddof=1) / np.sqrt(len(costs)))
for name, costs in cl_costs.items()
}
controller_names = list(cl_mean)
controller_values = np.array([cl_mean[name] for name in controller_names])
controller_errors = np.array([cl_sem[name] for name in controller_names])
prediction_to_controller = {
"M_obs (2 comps)": "MPC M_obs (2 comps)",
"M_obs (4 comps)": "MPC M_obs (4 comps)",
"M_state (relevant)": "MPC M_state (relevant)",
}
scatter_names = list(prediction_to_controller)
scatter_x = np.array([agg_mse[name] for name in scatter_names])
scatter_y = np.array(
[cl_mean[prediction_to_controller[name]] for name in scatter_names]
)PD gains selected on tuning episodes: (2.0, 0.5)
controller mean cost SEM
PD (no model) 3.222 0.795
MPC M_obs (2 comps) 9.769 1.841
MPC M_obs (4 comps) 2.596 0.810
MPC M_state (relevant) 2.596 0.810
MPC oracle 2.596 0.810
Pairwise aggregate error: {'M_obs (2 comps)': 31.793339997706347, 'M_state (relevant)': 342.10720636320696}
Pairwise control cost: {'M_obs (2 comps)': 9.769208180140867, 'M_state (relevant)': 2.596215849016851}The SEMs describe sampling variability over the twenty simulated episodes, conditional on these fitted models and this fixed experiment. They do not measure variation over real systems or independent training runs. Common random numbers can reduce comparison noise when the paired outcomes have positive covariance; they do not remove environmental randomness or guarantee smaller variance for every pair. Estimate uncertainty in the paired cost differences when assessing a particular comparison.


Reading the Result Without Overclaiming
The two-component observation model retains little of the controlled state and its controller incurs greater cost than the state-focused model in this test. Yet completing the state-focused model's nuisance outputs with the training mean penalizes it heavily in aggregate observation error. This pair reverses the error-versus-control ordering. The four-component model is a useful counterpoint: retaining the relevant subspace can make an observation model useful for control too. There is no claim that the aggregate metric is flat across dimension counts, or that its globally best scorer must be the worst controller.
Several design choices matter. We specified a large background whose transition law is unaffected by actions; gave the state-focused learner task-coordinate knowledge; used a particular output-completion rule; and chose one goal family and planner. Changing any of these can change the rankings. In particular, adding a nuisance predictor to the state-focused model can lower its aggregate error without changing its decisions. Normalizing channels or using task-sensitive loss weights are possible alternatives, but this notebook has not trained and tested those alternatives, so it does not claim they recover a ranking.
Report coordinate-wise errors alongside the full observation score and evaluate actions in an independent environment. Good one-step predictions do not certify multi-step reliability, calibration, or robustness after a dynamics change. Conversely, an aggregate score containing coordinates outside a model's declared prediction contract may say little about its intended consumer. The example makes that distinction inspectable with code; it supplies no hardware evidence or architecture-wide conclusion.