diff --git a/crates/benilla-app/src/character_shadow.rs b/crates/benilla-app/src/character_shadow.rs index b0477447..cdb5452f 100644 --- a/crates/benilla-app/src/character_shadow.rs +++ b/crates/benilla-app/src/character_shadow.rs @@ -1,27 +1,24 @@ //! The CHARACTER shadow lane — a thin plug-in on top of [`super::shadow_core`]. //! -//! It owns the per-unit caster for players, NPCs, creatures and mounts. When `characterShadows` is -//! on it declares demand to the shared rig ([`ShadowDemand`]); while the rig is live it keeps one -//! GPU-skinned caster per admitted creature part, drawn through [`SkinnedShadowCasterMaterial`]. -//! -//! The caster carries the part's own skinned mesh and `MeshTag`, so it skins in the shadow pass's -//! VERTEX stage from the shared rig palette the forward model already uploads — no per-frame CPU -//! skinning, no merged mesh, no re-upload. Pose changes are free: only membership (spawn/despawn, -//! range, occluder) is reconciled here. It does not know [`super::world_shadow`] exists. +//! It owns nothing but its own per-frame caster (players, NPCs, creatures, mounts). When +//! `characterShadows` is on it declares demand to the shared rig ([`ShadowDemand`]); while the rig +//! is live it collects the CREATURE entity parts into its caster using the rig's shared material and +//! the per-frame facts the rig publishes ([`ShadowFrame`]). It does not know [`super::world_shadow`] +//! exists — remove either lane and the other is untouched. use bevy::ecs::entity::EntityHashSet; -use bevy::mesh::MeshTag; -use bevy::platform::collections::HashMap; +use bevy::pbr::MeshMaterial3d; use bevy::prelude::*; +use benilla_assets::materials::WowModelMaterial; use benilla_world::billboard::BillboardCard; -use benilla_world::lighting::SharedLightBuffer; +use benilla_world::interact::PickMesh; use benilla_world::model_render::{ModelPart, ShadowOccluder}; -use benilla_world::rig_palette::RigPart; +use benilla_world::rig_palette::{RigPalettes, RigPart, RigSkin}; use crate::shadow_core::{ - casts_realtime_shadow, shadow_trace, spawn_skinned_caster, ShadowDemand, ShadowFrame, - ShadowSet, SkinnedShadowCasterMaterial, + collect_entity_geometry, empty_shadow_mesh, restore_mesh_buffers, shadow_trace, + spawn_solid_caster, take_mesh_buffers, RebuildRate, ShadowDemand, ShadowFrame, ShadowSet, }; use crate::video::VideoConfig; @@ -35,132 +32,141 @@ impl Plugin for CharacterShadowPlugin { } } -/// MONKEY (moon shadows): roots/ancestors with a live caster in the LAST completed reconciliation. -/// Blobs consume this in PostUpdate, before this frame's Last pass: a newly spawned caster gets a -/// render frame before its oval yields. +/// MONKEY (moon shadows): roots/ancestors with triangles in the LAST completed caster build. +/// Blobs consume this in PostUpdate, before this frame's Last rebuild: a newly uploaded proxy +/// gets a render frame before its oval yields. Cadence skips retain the same geometry/verdict. #[derive(Resource, Default)] pub(crate) struct CharacterShadowReady(pub EntityHashSet); -/// The character lane's retained casters: part entity → its shadow-caster entity. +/// The character lane's retained per-frame caster. #[derive(Resource, Default)] struct CharacterLane { - casters: HashMap, - material: Option>, - /// [`shadow_trace`]'s change detector: (admitted, rejected, casters). + caster: Option, + mesh: Option>, + /// [`shadow_trace`]'s change detector: (tris, admitted, rejected). traced: (u32, u32, u32), -} - -/// Drop every caster and the shared material handle. -fn teardown(commands: &mut Commands, lane: &mut CharacterLane) { - for (_, caster) in lane.casters.drain() { - commands.entity(caster).despawn(); - } - lane.material = None; + /// MONKEY (sun shadow perf): the `characterShadowRate` cadence gate — see the rebuild comment + /// in [`collect_character_shadows`]. + rate: RebuildRate, } #[allow(clippy::too_many_arguments)] fn collect_character_shadows( video: Res, + time: Res