Add plugin skeleton with versioned skill directories, TODO SKILL.md scaffolds with goals for skill-creator, and copy over bevy-skill-research reports from spacecraft into docs/workpad/research/.
5.1 KiB
Bevy Skill Research: Camera, Rendering & Common Gotchas
Bevy version: 0.18, with 0.19 notes
Date: 2026-07-07
Camera Setup
Camera3d is a marker component with #[require(...)] that auto-inserts: Camera, DebandDither, CameraRenderGraph, Projection, Tonemapping, ColorGrading, Exposure. Just spawning Camera3d gives a working 3D camera.
Coordinate system: right-handed, X-right, Y-up, Z-back (forward = -Z).
0.18 change: RenderTarget moved from a Camera field to a separate required component.
0.19 change: Hdr moved from bevy_render to bevy_camera; screen_space_specular_transmission_* fields extracted into ScreenSpaceTransmission component.
Camera Ordering / Multiple Cameras
Camera.order(isize) controls render order. Higher values render later (on top)- Each camera with
is_active: truerenders independently ClearColorConfigcontrols whether a camera clears the previous output- For overlays: use
ClearColorConfig::Noneon higher-order cameras
Camera Gotchas
- Default
farplane is 1000 units — objects beyond are culled silently - Forward is -Z, not +Z
- Viewport coordinates are in physical pixels, not logical
- GLTF models use +Z as visual forward (opposite Bevy convention)
Coordinate System
| Axis | Bevy native | GLTF models |
|---|---|---|
| Forward | -Z (Transform::forward()) |
+Z (Transform::back()) |
| Up | +Y | +Y |
| Right | +X | -X (from camera perspective) |
GLTF orientation fix: Use look_to(-velocity_dir, Vec3::Y) (negate direction) or read transform.back() for the model's visual forward.
Rendering / Mesh / Material Gotchas
- Mesh requires normals for
StandardMaterial. Missing normals = invisible mesh, no error - GLTF scenes must be spawned with scene labels (
"model.gltf#Scene0"), not the raw asset handle - Back-face culling is on by default; incorrect vertex winding = invisible faces
- Visibility hierarchy: parent entities need visibility components or children are invisible
- 0.18: Mesh access methods became
try_*returningResult<_, MeshAccessError>.MaterialPluginfields (prepass_enabled,shadows_enabled) becameMaterialtrait methods - 0.19: GLTF material loading returns
GltfMaterial(use#Material0/stdsuffix forStandardMaterial).Assets::get_mutreturnsAssetMut<A>with mutation tracking.Scene/SceneRootrenamed toWorldAsset/WorldAssetRoot
Transform Gotchas
- Bevy: +Y up, -Z forward, right-handed
Transform::forward()returns -Z,Transform::back()returns +Z- Transform propagation runs in PostUpdate — reading
GlobalTransforminUpdategives stale (last-frame) values - 0.18:
GltfPlugincoordinate conversion restructured —use_model_forward_directionreplaced byconvert_coordinateswithrotate_scene_entityandrotate_meshesflags
Asset Loading Gotchas
- Assets are NOT ready immediately after
asset_server.load(). Must checkAssetEvent::LoadedWithDependenciesor useAssetServer::is_loaded_with_dependencies() - 0.18:
LoadContext::path()returnsAssetPathnotPath. Imagereinterpret_*methods returnResult - 0.19:
AssetPath::resolve()/resolve_embed()take&AssetPathnot&str; string variants renamed toresolve_str()/resolve_embed_str()
Lighting Gotcha
AmbientLight split (0.18):
GlobalAmbientLight— resource, affects everythingAmbientLight— camera component, per-camera ambient
General LLM Traps
These are patterns where LLMs most commonly produce incorrect code:
| Trap | Wrong | Correct |
|---|---|---|
| Parent component | Parent |
ChildOf(entity) (0.16+) |
| Child iteration | children.iter().copied() |
children.into_iter() |
| Event writer | EventWriter<T> |
MessageWriter<T> (0.18+) |
| Event reader | EventReader<T> |
MessageReader<T> (0.18+) |
| Run condition | on_event::<T>() |
on_message::<T>() (0.18+) |
| Despawn recursive | despawn_recursive() |
despawn() (auto-recursive 0.16+) |
| Bundles | #[derive(Bundle)] |
Tuples + #[require(...)] (0.16+) |
| WinitPlugin | WinitPlugin::<WakeUp> |
WinitPlugin (not generic 0.18+) |
| GltfPlugin | init_asset::<Gltf>() |
GltfPlugin::default() (0.18+) |
| Material bind group | @group(2) in shader |
@group(3) (group 2 is mesh in 0.18) |
| System error handling | Return nothing | -> Result (0.18+) |
| GlobalTransform timing | Read in Update | Read in PostUpdate or accept lag |
| Same-state transition | state.set(current) no-ops |
Triggers OnExit/OnEnter in 0.18; use set_if_neq() |
0.19-Specific Traps
| Trap | Detail |
|---|---|
| Resources as components | Query<Entity> now matches resource entities — filter with Without<IsResource> |
| Scene renamed | Scene/SceneRoot → WorldAsset/WorldAssetRoot |
| Ref clone | Ref<T>.clone() returns Ref<T>, not inner value. Use ref.deref().clone() |
| Assets::get_mut | Returns AssetMut<A> with mutation tracking, not &mut A directly |
| GLTF materials | Returns GltfMaterial, use #Material0/std suffix for StandardMaterial |