What happens
With Matplotlib 3.11, the default matplotlib renderer crashes on the first frame:
File "mpl_toolkits/mplot3d/art3d.py", line 363, in draw
(len(self._verts3d), *self._verts3d[0].shape)
AttributeError: 'list' object has no attribute 'shape'
The whole run dies, so a competitor loses the evaluation. README describes matplotlib as the renderer that needs no extra, and example_eval_cfg.yaml ships with render_mode: "matplotlib", so this is the default path.
Reproduced by running the shipped config with Matplotlib 3.11.1 on Python 3.14. It does not reproduce on 3.10.9, which is why local runs are fine and CI is not.
Root cause
Two conditions have to hold together, and balloon_world.py produces both.
The rocket line's _verts3d holds lists rather than arrays. _render_frame updates it with:
self.render_rocket[0].set_data(
[self._rocket_states[0]], [self._rocket_states[1]]
)
self.render_rocket[0].set_3d_properties([self._rocket_states[2]])
A coordinate is invalid for the current axis scale. _rocket_states is np.full(13, np.nan) until the launch action builds the flight, and frames are drawn before that. In Line3D.draw the .shape access sits inside if np.any(scale_mask):, so a finite coordinate never reaches it. That is why passing lists went unnoticed for so long: it only breaks while the rocket has no state yet.
Isolated on 3.11.1:
| coordinates |
_verts3d contents |
result |
| finite |
lists |
draws |
| NaN |
lists |
AttributeError: 'list' object has no attribute 'shape' |
| NaN |
arrays |
draws |
Fix
Pass arrays. set_data_3d(np.array([x]), np.array([y]), np.array([z])) covers both calls and draws cleanly with NaN on 3.11.1.
Initialising the line with plot(scalar, scalar, scalar) at line 472 is worth tidying at the same time, though it is not what breaks: plot([x], [y], [z]) is the documented form.
Test note
A test that renders a NaN frame passes on Matplotlib 3.10 whether or not the fix is applied, since the crashing branch is only reachable on 3.11. Asserting that _verts3d entries are ndarray pins the fix on every version, which is what the fix actually guarantees.
Scope
Found while making the logging test in #88 hermetic; that test was driving the real CLI, which is how it surfaced. #88 no longer renders anything, so it is not blocked on this. #86 covers the vpython renderer and does not touch this branch.
What happens
With Matplotlib 3.11, the default
matplotlibrenderer crashes on the first frame:The whole run dies, so a competitor loses the evaluation. README describes
matplotlibas the renderer that needs no extra, andexample_eval_cfg.yamlships withrender_mode: "matplotlib", so this is the default path.Reproduced by running the shipped config with Matplotlib 3.11.1 on Python 3.14. It does not reproduce on 3.10.9, which is why local runs are fine and CI is not.
Root cause
Two conditions have to hold together, and
balloon_world.pyproduces both.The rocket line's
_verts3dholds lists rather than arrays._render_frameupdates it with:A coordinate is invalid for the current axis scale.
_rocket_statesisnp.full(13, np.nan)until the launch action builds the flight, and frames are drawn before that. InLine3D.drawthe.shapeaccess sits insideif np.any(scale_mask):, so a finite coordinate never reaches it. That is why passing lists went unnoticed for so long: it only breaks while the rocket has no state yet.Isolated on 3.11.1:
_verts3dcontentsAttributeError: 'list' object has no attribute 'shape'Fix
Pass arrays.
set_data_3d(np.array([x]), np.array([y]), np.array([z]))covers both calls and draws cleanly with NaN on 3.11.1.Initialising the line with
plot(scalar, scalar, scalar)at line 472 is worth tidying at the same time, though it is not what breaks:plot([x], [y], [z])is the documented form.Test note
A test that renders a NaN frame passes on Matplotlib 3.10 whether or not the fix is applied, since the crashing branch is only reachable on 3.11. Asserting that
_verts3dentries arendarraypins the fix on every version, which is what the fix actually guarantees.Scope
Found while making the logging test in #88 hermetic; that test was driving the real CLI, which is how it surfaced. #88 no longer renders anything, so it is not blocked on this. #86 covers the vpython renderer and does not touch this branch.