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 {