diff --git a/crates/benilla-app/src/blob_shadow.rs b/crates/benilla-app/src/blob_shadow.rs index 821c6747..effa6501 100644 --- a/crates/benilla-app/src/blob_shadow.rs +++ b/crates/benilla-app/src/blob_shadow.rs @@ -11,7 +11,11 @@ //! [`GroundDecalSurface`] set yet — the shadow lands on terrain + WMO faces only). //! - **Frame slot**: PHASE 1, among the opaque drains — `0x6812c5 call 0x683dd0`, fifth of the //! row `0x6812b1`–`0x6812ca` inside `0x681070`, which the driver `0x483460` calls at -//! `0x48361d` (wow-re `water-frame-straddle.md` §1 + `unit-blob-shadow.md` Q1). So the shadow +//! `0x48361d` (wow-re `water-frame-straddle.md` §1 + `unit-blob-shadow.md` Q1). `0x683dd0` is +//! the **M2 node drain**, and the same loop body ticks each node's object first +//! (`0x48160c call [obj vt+0x38]` → the selection ring) and gates its shadow second +//! (`0x683ec3`) — so per unit the additive ring goes down and this modulate darkens it, never +//! the other way round (wow-re `decal-frame-slot.md`). So the shadow //! lands after terrain and WMO and **before** everything else: the footprint decals //! (`0x483654`), the M2 opaque pass (`0x4836a6`), the water surfaces (phase 3, drawn *between* //! the two M2 transparent passes) and both of those passes. Every transparent in the world diff --git a/crates/benilla-app/src/capture/mod.rs b/crates/benilla-app/src/capture/mod.rs index bcf5a423..9eaf2f91 100644 --- a/crates/benilla-app/src/capture/mod.rs +++ b/crates/benilla-app/src/capture/mod.rs @@ -36,6 +36,12 @@ //! quiescence, or waiting for pixel stability cannot help. It is a renderer defect that the harness //! correctly *reports*; do not try to gate it away here. Read 0815 before touching this file's phases. //! +//! **`creature-indoor-front` is one of them** (named 2026-09-01, while A/B-ing 1787's fog gate): two +//! runs of ONE unchanged build land in the two states at MAE 4.518, the whole difference confined to +//! the subject's own brightness with the room around it pixel-identical, and the settle counts differ +//! (235 vs 211 frames). If a change you are A/B-ing shows exactly 4.518 here, re-run the baseline +//! before believing it — the number is the flake's, not yours. +//! //! ## Running one capture by hand //! **Run through Cargo — never the built binary directly:** //! ```text diff --git a/crates/benilla-app/src/creature_anim/lod.rs b/crates/benilla-app/src/creature_anim/lod.rs index 23220b20..2eab8a22 100644 --- a/crates/benilla-app/src/creature_anim/lod.rs +++ b/crates/benilla-app/src/creature_anim/lod.rs @@ -597,6 +597,7 @@ mod tests { world_from_local: bevy::math::Affine3A::IDENTITY, name_set: 0, visible: vec![false, false], + interior_fog: vec![false, false], liquid_visited: vec![false, false], flooded: vec![None, None], }) diff --git a/crates/benilla-app/src/doodad_events.rs b/crates/benilla-app/src/doodad_events.rs new file mode 100644 index 00000000..1b1a7786 --- /dev/null +++ b/crates/benilla-app/src/doodad_events.rs @@ -0,0 +1,71 @@ +//! The **placed-doodad** animation-event scanner — the third and last producer feeding the +//! [`AnimSoundEvent`] stream, beside the creature scanner ([`crate::creature_anim`]) and the +//! GameObject one ([`crate::go_anim`]). +//! +//! Bug B345 ("every world-placed M2 doodad is silent") was this hole: a lamp's hum, a campfire's +//! crackle, a waterfall, a windmill and a gnome machine are all authored as `$DSL`/`$DSO`/`$SND` +//! keys on the model's own idle sequence — and **nothing in benilla ever wrote a doodad's events +//! into the stream**. The routing end ([`crate::sound::anim_events`]) has handled those tags since +//! decision 0070 slice 3; it simply never received one from a doodad, because the only two +//! scanners gate on `AnimDriver` (units) and `GoAnim` (server-sent GameObjects), neither of which a +//! terrain-stream placement ever gets. +//! +//! **Why it is not enough to scan the hosts that already exist.** The corpus says 273 models author +//! a doodad sound marker (`benilla-extract soundeventscan`); only 165 of them are the `FirstSeq` +//! tier that carries an `AnimationPlayer`. The other 108 — including the reported lamp, +//! `KalidarStreetLamp01.m2`, one looping 3.333 s Stand that keys no bone at all and carries exactly +//! one `$DSL` → `NightElfStreetLampLoop` — are bind-posed, and decision 0130's content gate +//! deliberately builds them no rig. That gate is right about pixels and was never a claim about the +//! clock: the reference arms **every** placed doodad and cycles it (wow-re `doodad-anim-host.md` +//! §1/§5). So `doodad_anim::spawn_anim_host` now gives a sound-carrying model a clock-only host — +//! the arm bookkeeping, no player — and this scanner reads that arm. +//! +//! **The clock is the shared one, never the player** ([`DoodadAnimHost::arm_clock`]): the reference +//! gates the cycle on *linkage* (residency), not on the draw, so a campfire behind you keeps +//! crackling. Reading the draw-gated `AnimationPlayer` would silence it the moment you turned away. + +use bevy::prelude::*; + +use benilla_world::doodad_anim::DoodadAnimHost; +use benilla_world::schedule::WorldStage; + +use crate::creature_anim::{advance_track, scan_events, AnimSoundEvent, TrackMemory}; +use benilla_assets::ModelAnimations; + +/// Fire the event keyframes each placed doodad's armed clip crossed this frame. +/// +/// The arming rules are the shared [`advance_track`]/[`scan_events`] ones the other two scanners +/// use — an arm frame fires nothing, the frame after it opens the clip's `t = 0` head (which is +/// where essentially every doodad sound key sits), and a loop wrap fires tail-then-head. A +/// variation re-roll ([`benilla_world::doodad_anim`]'s self-sustaining re-arm) changes the node, so +/// [`advance_track`] sees a fresh arm and never scans across the seam. +fn fire_doodad_anim_events( + time: Res