From 8be58bde18388ac3b0116b74298592d2586895ce Mon Sep 17 00:00:00 2001 From: pkuzic Date: Sat, 26 Sep 2026 16:51:30 +0100 Subject: [PATCH] MONKEY (leftovers): warm the two sun-shadow proxy lanes in the menagerie ShadowCasterMaterial and CutoutShadowCasterMaterial get one warm rig each (production attribute sets, layers 0+31: the world camera mints the forward pipeline, a live sun rig's shadow views the depth one). The cutout pair (MAY_DISCARD opaque + prepass) otherwise first compiles when the first leaf card enters sun-rig reach, after the cover lifts. Fixes the failing pipe_warm::menagerie::every_material_lane_has_a_warm_contributor census for real. Co-Authored-By: Claude Opus 5.5 --- crates/benilla-app/src/pipe_warm/menagerie.rs | 51 +++++++++++++++++++ crates/benilla-app/src/world_shadow.rs | 5 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/crates/benilla-app/src/pipe_warm/menagerie.rs b/crates/benilla-app/src/pipe_warm/menagerie.rs index 8701048e..43b0195b 100644 --- a/crates/benilla-app/src/pipe_warm/menagerie.rs +++ b/crates/benilla-app/src/pipe_warm/menagerie.rs @@ -63,6 +63,11 @@ pub(super) struct WarmLanes<'w> { /// The UI quad store (2262): the minimap interior composite's tile material is built here /// through its own production builder, for the tile-quad warm rig below. ui_quads: ResMut<'w, Assets>, + /// MONKEY (leftovers): the two sun-shadow proxy lanes, `ShadowCasterMaterial` (solid) and + /// `CutoutShadowCasterMaterial` (alpha-tested leaves). + /// `Option`: an app without the shadow plugins (tests, the glue) has no store. + shadow_solid: Option>>, + shadow_cutout: Option>>, } /// One portrait/paperdoll booth camera + its layer ([`crate::portrait`], 0938): the booths run @@ -700,6 +705,52 @@ pub(super) fn spawn_menagerie( )); count += 1; + // MONKEY (leftovers): the sun-shadow proxy lanes. Production proxies live on the private + // layer 31, which the sun rig's directional light casts from and the world camera also draws + // (forward discards every fragment). The solid proxy usually compiles at entry anyway (the + // player's own caster), but the cutout one first appears with the first leaf card in reach, + // which can be long after the cover lifts. Each rig sits on layers 0 and 31: the world camera + // mints the forward pipeline, the shadow views (when a sun rig is live) the depth one. Meshes + // are the production attribute sets (`empty_shadow_mesh` / `empty_cutout_mesh`), non-empty. + let proxy_layers = bevy::camera::visibility::RenderLayers::from_layers(&[ + 0, + crate::shadow_core::PLAYER_SHADOW_LAYER, + ]); + let tri = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]]; + let mut solid_mesh = crate::shadow_core::empty_shadow_mesh(); + solid_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, tri.to_vec()); + solid_mesh.insert_indices(Indices::U32(vec![0, 1, 2])); + let mut cutout_mesh = crate::shadow_core::empty_cutout_mesh(); + cutout_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, tri.to_vec()); + cutout_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0f32, 0.0], [1.0, 0.0], [1.0, 1.0]]); + cutout_mesh.insert_indices(Indices::U32(vec![0, 1, 2])); + if let Some(store) = lanes.shadow_solid.as_mut() { + let solid = store.add(crate::shadow_core::ShadowCasterMaterial {}); + spawn_lane_rig( + commands, + cam, + Some(proxy_layers.clone()), + &meshes.add(solid_mesh), + None, + solid, + &mut count, + ); + } + if let Some(store) = lanes.shadow_cutout.as_mut() { + // A real stand-in leaf, so the `#[texture]` binding never lands on the retry path. + let leaf = lanes.images.add(Image::default()); + let cutout = store.add(crate::world_shadow::CutoutShadowCasterMaterial { leaf }); + spawn_lane_rig( + commands, + cam, + Some(proxy_layers), + &meshes.add(cutout_mesh), + None, + cutout, + &mut count, + ); + } + count } diff --git a/crates/benilla-app/src/world_shadow.rs b/crates/benilla-app/src/world_shadow.rs index e04aa5a4..8ab4a256 100644 --- a/crates/benilla-app/src/world_shadow.rs +++ b/crates/benilla-app/src/world_shadow.rs @@ -84,13 +84,14 @@ fn refresh_streamed_shadows( /// `prepass_fragment_shader` makes the otherwise depth-only directional-shadow pass run a fragment — /// the exact pair Bevy's prepass specializer requires to alpha-test in the shadow map. #[derive(Asset, AsBindGroup, TypePath, Debug, Clone)] -struct CutoutShadowCasterMaterial { +// MONKEY (leftovers): crate-visible for the pipe_warm menagerie. +pub(crate) struct CutoutShadowCasterMaterial { /// The leaf sheet, bound at material binding 0/1. Reusing the SAME image handle the forward pass /// draws makes the sampler inherit that image's clamp/repeat address mode, so the shadow /// silhouette matches the visible foliage (a cutout card's out-of-range UVs must clamp). #[texture(0)] #[sampler(1)] - leaf: Handle, + pub(crate) leaf: Handle, } impl Material for CutoutShadowCasterMaterial {