Files
g4borg 325e278af1 Scaffold bevy plugin with bevy-0.19 and bevy-upgrade skills
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/.
2026-07-07 17:52:00 +02:00

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: true renders independently
  • ClearColorConfig controls whether a camera clears the previous output
  • For overlays: use ClearColorConfig::None on higher-order cameras

Camera Gotchas

  • Default far plane 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_* returning Result<_, MeshAccessError>. MaterialPlugin fields (prepass_enabled, shadows_enabled) became Material trait methods
  • 0.19: GLTF material loading returns GltfMaterial (use #Material0/std suffix for StandardMaterial). Assets::get_mut returns AssetMut<A> with mutation tracking. Scene/SceneRoot renamed to WorldAsset/WorldAssetRoot

Transform Gotchas

  • Bevy: +Y up, -Z forward, right-handed
  • Transform::forward() returns -Z, Transform::back() returns +Z
  • Transform propagation runs in PostUpdate — reading GlobalTransform in Update gives stale (last-frame) values
  • 0.18: GltfPlugin coordinate conversion restructured — use_model_forward_direction replaced by convert_coordinates with rotate_scene_entity and rotate_meshes flags

Asset Loading Gotchas

  • Assets are NOT ready immediately after asset_server.load(). Must check AssetEvent::LoadedWithDependencies or use AssetServer::is_loaded_with_dependencies()
  • 0.18: LoadContext::path() returns AssetPath not Path. Image reinterpret_* methods return Result
  • 0.19: AssetPath::resolve()/resolve_embed() take &AssetPath not &str; string variants renamed to resolve_str()/resolve_embed_str()

Lighting Gotcha

AmbientLight split (0.18):

  • GlobalAmbientLight — resource, affects everything
  • AmbientLight — 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/SceneRootWorldAsset/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