Ah, my intuition for the local order of nodes was wrong.
For example, the following code
---
from firedrake import *
mesh = UnitIntervalMesh(4)
V = FunctionSpace(mesh, "CG", 4)
u = Function(V).interpolate(Expression("x[0]"))
print_kernel ="""
printf("%f %f %f %f %f\\n", u[0][0], u[1][0], u[2][0], u[3][0], u[4][0]);
"""
par_loop(print_kernel, dx, {"u": (u, RW)})
print u.dat.data
---
produces
0.250000 0.000000 0.187500 0.125000 0.062500
0.500000 0.250000 0.437500 0.375000 0.312500
0.750000 0.500000 0.687500 0.625000 0.562500
1.000000 0.750000 0.937500 0.875000 0.812500
(our IntervalMesh cells are 'backwards', just to make things more fun, but this shouldn't be the case in extruded meshes)
In other words, it looks like the CG 'ends' come before the middle, so try 0, 1, 3, 4.
Thanks for spotting this, because it means some of my own visualisation will be wrong!